diff options
Diffstat (limited to 'accel-pppd')
110 files changed, 8749 insertions, 2659 deletions
diff --git a/accel-pppd/CMakeLists.txt b/accel-pppd/CMakeLists.txt index cd7186ba..49c2ff61 100644 --- a/accel-pppd/CMakeLists.txt +++ b/accel-pppd/CMakeLists.txt @@ -44,32 +44,18 @@ IF (RADIUS) ADD_SUBDIRECTORY(radius) ENDIF (RADIUS) -# define __free_fn_t in musl INCLUDE (CheckCSourceCompiles) CHECK_C_SOURCE_COMPILES(" -#include <stdlib.h> -#include <search.h> +#include <utmp.h> int main(void) { - __free_fn_t *f; + logwtmp(\"\", \"\", \"\"); return 0; -}" HAVE_FREE_FN_T) -IF (HAVE_FREE_FN_T) - ADD_DEFINITIONS(-DHAVE_FREE_FN_T) -ENDIF (HAVE_FREE_FN_T) +}" HAVE_LOGWTMP) -INCLUDE (CheckCSourceCompiles) -CHECK_C_SOURCE_COMPILES(" -#include <linux/if_arp.h> -#include <net/ethernet.h> -int main(void) -{ - return 0; -}" HAVE_GOOD_IFARP) - -IF (HAVE_GOOD_IFARP) - ADD_DEFINITIONS(-DHAVE_GOOD_IFARP) -ENDIF (HAVE_GOOD_IFARP) +IF (HAVE_LOGWTMP) + ADD_DEFINITIONS(-DHAVE_LOGWTMP) +ENDIF (HAVE_LOGWTMP) ADD_SUBDIRECTORY(triton) @@ -90,12 +76,6 @@ ENDIF (SHAPER) INCLUDE(CheckIncludeFile) CHECK_INCLUDE_FILE("linux/netfilter/ipset/ip_set.h" HAVE_IPSET) -# MUSL does not have printf.h -CHECK_INCLUDE_FILE("printf.h" HAVE_PRINTF_H) -IF (HAVE_PRINTF_H) - ADD_DEFINITIONS(-DHAVE_PRINTF_H) -ENDIF (HAVE_PRINTF_H) - INCLUDE(CheckFunctionExists) CHECK_FUNCTION_EXISTS(setns HAVE_SETNS) @@ -163,9 +143,9 @@ CHECK_FUNCTION_EXISTS(getcontext HAVE_GETCONTEXT) CHECK_FUNCTION_EXISTS(setcontext HAVE_SETCONTEXT) IF (HAVE_GETCONTEXT AND HAVE_SETCONTEXT) - TARGET_LINK_LIBRARIES(accel-pppd triton rt pthread ${crypto_lib} pcre) + TARGET_LINK_LIBRARIES(accel-pppd triton rt pthread ${crypto_lib} pcre2-8) ELSE (HAVE_GETCONTEXT AND HAVE_SETCONTEXT) - TARGET_LINK_LIBRARIES(accel-pppd triton rt pthread ${crypto_lib} pcre ucontext) + TARGET_LINK_LIBRARIES(accel-pppd triton rt pthread ${crypto_lib} pcre2-8 ucontext) ENDIF (HAVE_GETCONTEXT AND HAVE_SETCONTEXT) set_property(TARGET accel-pppd PROPERTY CMAKE_SKIP_BUILD_RPATH FALSE) diff --git a/accel-pppd/accel-ppp.conf b/accel-pppd/accel-ppp.conf index abfd1ac9..fe124392 100644 --- a/accel-pppd/accel-ppp.conf +++ b/accel-pppd/accel-ppp.conf @@ -2,10 +2,13 @@ log_file #log_syslog #log_tcp -#log_pgsql +#log_pgsql (deprecated, scheduled for removal) connlimit +radius +#chap-secrets + pptp l2tp #sstp @@ -17,9 +20,6 @@ auth_mschap_v1 auth_chap_md5 auth_pap -radius -#chap-secrets - ippool pppd_compat @@ -27,6 +27,7 @@ pppd_compat #shaper #net-snmp #logwtmp +#metrics #ipv6_nd @@ -35,6 +36,7 @@ pppd_compat [core] log-error=/var/log/accel-ppp/core.log +#log-debug=/var/log/accel-ppp/debug.log thread-count=4 [common] @@ -42,11 +44,14 @@ thread-count=4 #single-session-ignore-case=0 #sid-case=upper #sid-source=seq +#seq-file=/var/lib/accel-ppp/seq #max-sessions=1000 #max-starting=0 +#session-timeout=0 #check-ip=0 #nl-snd-buffer=32768 #nl-rcv-buffer=1048576 +#netns-run-dir=/var/run/netns [ppp] verbose=1 @@ -56,7 +61,14 @@ mru=1400 #accomp=deny #pcomp=deny #ccp=0 +#ccp-max-configure=3 #mppe=require +#timeout=3 +#max-configure=10 +#max-failure=10 +#max-terminate=2 +#max-mtu=1500 +#check-ip=0 ipv4=require ipv6=deny ipv6-intf-id=0:0:0:1 @@ -78,7 +90,14 @@ unit-cache=1 [pptp] verbose=1 +#bind=0.0.0.0 +#port=1723 #echo-interval=30 +#echo-failure=3 +#timeout=5 +#mppe=allow +#ppp-max-mtu=1436 +#session-timeout=0 #ip-pool=pptp #ipv6-pool=pptp #ipv6-pool-delegate=pptp @@ -88,16 +107,23 @@ verbose=1 verbose=1 #ac-name=xxx #service-name=yyy +#accept-any-service=0 +#accept-blank-service=0 #pado-delay=0 #pado-delay=0,100:100,200:200,-1:500 called-sid=mac #tr101=1 #padi-limit=0 +#mac-filter=/path/to/mac-filter,allow +#mppe=allow +#session-timeout=0 #ip-pool=pppoe #ipv6-pool=pppoe #ipv6-pool-delegate=pppoe #ifname=pppoe%d +#ifname-in-sid=0 #sid-uppercase=0 +#cookie-timeout=5 #vlan-mon=eth0,10-200 #vlan-timeout=60 #vlan-name=%I.%N @@ -106,6 +132,8 @@ interface=eth0 [l2tp] verbose=1 +#bind=0.0.0.0 +#port=1701 #dictionary=/usr/local/share/accel-ppp/l2tp/dictionary #hello-interval=60 #timeout=60 @@ -116,8 +144,14 @@ verbose=1 #host-name=accel-ppp #dir300_quirk=0 #secret= +#hide-avps=0 #dataseq=allow #reorder-timeout=0 +#use-ephemeral-ports=0 +#ppp-max-mtu=1420 +#mppe=allow +#session-timeout=0 +#avp_permissive=0 #ip-pool=l2tp #ipv6-pool=l2tp #ipv6-pool-delegate=l2tp @@ -125,6 +159,8 @@ verbose=1 [sstp] verbose=1 +#bind=0.0.0.0 +#port=443 #cert-hash-proto=sha1,sha256 #cert-hash-sha1= #cert-hash-sha256= @@ -141,6 +177,11 @@ verbose=1 #http-error=allow #timeout=60 #hello-interval=60 +#ppp-mode=auto +#ppp-max-mtu=1452 +#sndbuf=0 +#rcvbuf=0 +#session-timeout=0 #ip-pool=sstp #ipv6-pool=sstp #ipv6-pool-delegate=sstp @@ -150,6 +191,7 @@ verbose=1 verbose=1 username=ifname #password=username +#netmask=24 lease-time=600 #renew-time=300 #rebind-time=525 @@ -159,6 +201,7 @@ max-lease-time=3600 #l4-redirect-ipset=l4 #l4-redirect-on-reject=300 #l4-redirect-ip-pool=pool1 +#noauth=0 shared=0 ifcfg=1 mode=L2 @@ -168,7 +211,12 @@ start=dhcpv4 #proxy-arp=0 #nat=0 #proto=100 +#src=192.0.2.1 #relay=10.10.10.10 +#relay-timeout=3 +#relay-retransmit=3 +#agent-remote-id=accel-ppp +#link-selection=0.0.0.0 #vendor=Custom #weight=0 #attr-dhcp-client-ip=DHCP-Client-IP-Address @@ -185,6 +233,7 @@ start=dhcpv4 #attr-l4-redirect-ipset=l4-redirect #lua-file=/etc/accel-ppp.lua #offer-delay=0,100:100,200:200,-1:1000 +#offer-timeout=10 #vlan-mon=eth0,10-200 #vlan-timeout=60 #vlan-name=%I.%N @@ -196,6 +245,8 @@ start=dhcpv4 #soft-terminate=0 #check-mac-change=1 #calling-sid=mac +#ipv6=0 +#check-ip=0 #local-net=192.168.0.0/16 interface=eth0 @@ -209,14 +260,21 @@ interface=eth0 [radius] #dictionary=/usr/local/share/accel-ppp/radius/dictionary +#framed-route-strict=0 nas-identifier=accel-ppp nas-ip-address=127.0.0.1 gw-ip-address=192.168.100.1 server=127.0.0.1,testing123,auth-port=1812,acct-port=1813,req-limit=50,fail-timeout=0,max-fail=10,weight=1 dae-server=127.0.0.1:3799,testing123 +#dae-allowed=127.0.0.1,192.0.2.0/24 +#dae-allowed is generally recommended to restrict DM/CoA sources, but not required. verbose=1 #timeout=3 #max-try=3 +#req-limit=0 +#fail-timeout=0 +#max-fail=10 +#interim-verbose=0 #acct-timeout=120 #acct-delay-time=0 #acct-delay-start=0 @@ -226,7 +284,11 @@ verbose=1 #default-realm= #strip-realm=0 #attr-tunnel-type=My-Tunnel-Type +#sid-in-auth=0 #nas-port-id-in-req=1 +#bind=0.0.0.0 +#require-nas-identification=0 +blast-protection=1 [client-ip-range] 10.0.0.0/8 @@ -236,6 +298,9 @@ gw-ip-address=192.168.0.1 #vendor=Cisco #attr=Cisco-AVPair attr=Framed-Pool +#shuffle=0 +# on reload, sessions whose address left the pools: keep (default) or disconnect +#reload-orphan=keep 192.168.0.2-255 192.168.1.1-255,name=pool1 192.168.2.1-255,name=pool2 @@ -259,6 +324,8 @@ level=3 [log-pgsql] conninfo=user=log +#connect-inteval=5 +#log-query=insert into log (timestamp, username, sessionid, msg) values ($1, $2, $3, $4) log-table=log [pppd-compat] @@ -278,6 +345,10 @@ gw-ip-address=192.168.100.1 [shaper] #attr=Filter-Id +#vendor=Cisco +#attr-down=PPPD-Downstream-Speed +#attr-up=PPPD-Upstream-Speed +#burst-factor=0.1 #down-burst-factor=0.1 #up-burst-factor=1.0 #latency=50 @@ -302,6 +373,8 @@ verbose=1 telnet=127.0.0.1:2000 tcp=127.0.0.1:2001 #password=123 +#prompt=accel-ppp +#history-file=/var/lib/accel-ppp/cli-history #sessions-columns=ifname,username,ip,ip6,ip6-dp,type,state,uptime,uptime-raw,calling-sid,called-sid,sid,comp,inbound-if,service-name,rx-bytes,tx-bytes,rx-bytes-raw,tx-bytes-raw,rx-pkts,tx-pkts,netns,vrf [snmp] @@ -313,11 +386,27 @@ limit=10/min burst=3 timeout=60 +#[metrics] +# Exposes daemon statistics over HTTP. Same numbers as "accel-cmd show stat". +# Enable by adding "metrics" to [modules] above. +#format=prometheus +#address=127.0.0.1:8080 +#allowed_ips=["127.0.0.1/32"] +#read_timeout=5 +#max_clients=64 +# Include per-session identity and traffic data in JSON output. This has no +# effect with format=prometheus. Disabled by default because usernames and +# station IDs may be sensitive: restrict allowed_ips before enabling it. +# Traffic counters are the ones last sampled by accounting, so their freshness +# follows radius interim-interval / idle-timeout. +#sessions=0 + [ipv6-pool] #gw-ip6-address=fc00:0:1::1 #vendor= #attr-prefix=Delegated-IPv6-Prefix-Pool #attr-address=Stateful-IPv6-Address-Pool +#reload-orphan=keep fc00:0:1::/48,64 fc00:0:2::/48,64,name=pool1 fc00:0:3::/48,64,name=pool2,next=pool1 @@ -326,14 +415,36 @@ delegate=fc00:2::/36,48,name=pool3 delegate=fc00:3::/36,48,name=pool4,next=pool3 [ipv6-dns] +# Used for sessions which were not assigned their own DNS servers, e.g. by the +# RADIUS DNS-Server-IPv6-Address attribute (RFC 6911) #fc00:1::1 #fc00:1::2 #fc00:1::3 #dnssl=suffix1.local.net #dnssl=suffix2.local.net. +#lifetime=600 [ipv6-dhcp] verbose=1 pref-lifetime=604800 valid-lifetime=2592000 route-via-gw=1 +#aftr-gw=aftr.example.net +#server-id=0:0:0:1 + +[ipv6-nd] +#MaxRtrAdvInterval=600 +#MinRtrAdvInterval=198 +#MaxInitialRtrAdvCount=5 +#MaxInitialRtrAdvInterval=3 +#AdvManagedFlag=0 +#AdvOtherConfigFlag=0 +#AdvLinkMTU=0 +#AdvReachableTime=0 +#AdvRetransTimer=0 +#AdvCurHopLimit=64 +#AdvDefaultLifetime=1800 +#AdvValidLifetime=2592000 +#AdvPreferredLifetime=604800 +#AdvOnLinkFlag=1 +#AdvAutonomousFlag=1 diff --git a/accel-pppd/accel-ppp.conf.5 b/accel-pppd/accel-ppp.conf.5 index 72376c35..5ee7fd15 100644 --- a/accel-pppd/accel-ppp.conf.5 +++ b/accel-pppd/accel-ppp.conf.5 @@ -36,6 +36,7 @@ This is logging target which logs messages over TCP/IP. .TP .BI log_pgsql This is logging target which logs messages to PostgreSQL. +Deprecated and scheduled for removal, see the [log-pgsql] section. .TP .BI pptp .br @@ -49,6 +50,14 @@ PPPoE discovery stage handling module. .br SSTP controlling connection handling module. .TP +.BI l2tp +.br +L2TPv2 connection handling module. +.TP +.BI ipoe +.br +IPoE connection handling module. +.TP .BI auth_pap PAP authentication module. .TP @@ -65,6 +74,10 @@ Microsoft CHAP (version 2) authentication module. .br RADIUS interaction module. .TP +.BI chap-secrets +.br +Authentication and address assignment from a pppd-compatible chap-secrets file. +.TP .BI ippool .br IPv4 address assigning module. @@ -77,6 +90,26 @@ IPv6 address assigning module. .br IPv6 DHCP module. .TP +.BI ipv6_nd +.br +IPv6 Neighbor Discovery and Router Advertisement module. +.TP +.BI shaper +.br +Traffic shaping and policing module. +.TP +.BI net-snmp +.br +SNMP agent module. It must be built with NETSNMP enabled. +.TP +.BI logwtmp +.br +Module that writes session records to wtmp. +.TP +.BI metrics +.br +HTTP statistics endpoint module. +.TP .BI sigchld Helper module to manage child processes, required by pppd_compat .TP @@ -91,6 +124,9 @@ Configuration of core module .BI "log-error=" path Path to file for core module error logging. .TP +.BI "log-debug=" path +Path to file for core module debug logging. +.TP .BI "thread-count=" n number of working threads, optimal - number of processors/cores .SH [common] @@ -116,7 +152,7 @@ Specifies whether accel-ppp should ignore the case when comparing username withi Specifies in which case generate session identifier (default lower). .TP .BI "sid-source=" urandom|seq -Specifies method assign session id (default urandom). +Specifies method used to assign session IDs (default seq). .br .B urandom Assign session id by random method @@ -135,6 +171,7 @@ Specifies maximum concurrent session attempts which server may processed (defaul .TP .BI "check-ip=" 0|1 Specifies whether accel-ppp should check if IP already assigned to other client interface (default 0). +Check is global (not VRF-aware); with check-ip=1 the same IPv4/IPv6 address will be rejected even if sessions are in different VRFs/netns. With check-ip=0 overlapping addresses can be assigned (useful for isolated VRFs, but take care with overlapping pools). This setting can be overridden by module-specific 'check-ip' options (e.g., in the [ppp] section). .TP .BI "nl-snd-buffer=" n Specifies netlink maximum send buffer size (SO_SNDBUF option) (default 32768). @@ -142,6 +179,9 @@ Specifies netlink maximum send buffer size (SO_SNDBUF option) (default 32768). .BI "nl-rcv-buffer=" n Specifies netlink maximum receive buffer size (SO_RCVBUF option) (default 1048576). .TP +.BI "netns-run-dir=" path +Directory containing named network namespace handles (default /var/run/netns). +.TP .BI "session-timeout=" n Specifies max sessions time in seconds, after this time session will be terminated. .br @@ -183,6 +223,12 @@ Protocol field compression negotiation. .BI "ccp=" n Disable CCP negotiation if this parameter is zero. .TP +.BI "ccp-max-configure=" n +Specifies the maximum number of CCP Configure-Request packets sent without receiving a valid Configure-Ack, Configure-Nak or Configure-Reject before assuming the peer is unable to respond (default 3). +.TP +.BI "timeout=" n +Specifies the timeout, in seconds, used by PPP finite-state machines (default 3). +.TP .BI "mppe=" require|prefer|deny Specifies mppe negotiation preference. .br @@ -198,6 +244,21 @@ Specifies mppe negotiation preference. Default behavior - don't ask client for mppe, but allow it if client wants. Please note that RADIUS may override this option by MS-MPPE-Encryption-Policy attribute. .TP +.BI "max-configure=" n +Specifies the maximum number of LCP/IPCP Configure-Request packets sent without receiving a valid Configure-Ack, Configure-Nak or Configure-Reject before assuming the peer is unable to respond (default 10). +.TP +.BI "max-failure=" n +Specifies the maximum number of LCP/IPCP Configure-Nak returned before starting to send Configure-Reject (default 10). +.TP +.BI "max-terminate=" n +Specifies the maximum number of Terminate-Request packets sent without receiving a Terminate-Ack before assuming the peer has disconnected (default 2). +.TP +.BI "max-mtu=" n +Specifies the absolute maximum MTU value that can be negotiated. +.TP +.BI "check-ip=" 0|1 +Specifies whether to check if the assigned IP address is already in use by another session (overrides [common] check-ip for PPP sessions). +.TP .BI "ipv4=" deny|allow|prefer|require Specify IPv4 (IPCP) negotiation algorithm: .br @@ -521,6 +582,81 @@ If enabled accel-ppp will terminate session when detects change of mac address o .TP .BI "calling-sid=" mac|ip Specifies value of Calling-Station-Id attribute. +.TP +.BI "netmask=" n +Specifies the IPv4 prefix length assigned to IPoE sessions (1 through 32). +.TP +.BI "nat=" 0|1 +Marks IPoE sessions as requiring NAT. +.TP +.BI "noauth=" 0|1 +Authorizes IPoE sessions without consulting a password database. The default is 0. +.TP +.BI "check-ip=" 0|1 +Overrides the common duplicate-address check for IPoE sessions. +.TP +.BI "ipv6=" 0|1 +Enables IPv6 on IPoE interfaces by default. +.TP +.BI "src=" x.x.x.x +Specifies the source address used by the IPoE service. +.TP +.BI "relay-timeout=" n +Specifies the DHCP relay reply timeout in seconds (default 3). +.TP +.BI "relay-retransmit=" n +Specifies the maximum number of DHCP relay retransmissions (default 3). +.TP +.BI "ip-pool=" pool_name +Specifies the default IPv4 address pool. +.TP +.BI "ipv6-pool=" pool_name +Specifies the default IPv6 address pool. +.TP +.BI "ipv6-pool-delegate=" pool_name +Specifies the default delegated IPv6 prefix pool. +.TP +.BI "lua-file=" path +Loads the Lua file that supplies IPoE Lua hooks. Lua support must be built in. +.TP +.BI "vendor=" vendor +Specifies the RADIUS vendor used when resolving the IPoE attribute options below. +.TP +.BI "attr-dhcp-client-ip=" attribute +Specifies the RADIUS attribute containing the DHCP client address. +.TP +.BI "attr-dhcp-router-ip=" attribute +Specifies the RADIUS attribute containing the DHCP router address. +.TP +.BI "attr-dhcp-mask=" attribute +Specifies the RADIUS attribute containing the DHCP subnet mask. +.TP +.BI "attr-dhcp-lease-time=" attribute +Specifies the RADIUS attribute containing the DHCP lease time. +.TP +.BI "attr-dhcp-renew-time=" attribute +Specifies the RADIUS attribute containing the DHCP renewal time. +.TP +.BI "attr-dhcp-rebind-time=" attribute +Specifies the RADIUS attribute containing the DHCP rebinding time. +.TP +.BI "attr-dhcp-opt82=" attribute +Specifies the RADIUS attribute used for the complete DHCP Option 82 value. +.TP +.BI "attr-dhcp-opt82-remote-id=" attribute +Specifies the RADIUS attribute used for the Option 82 remote ID. +.TP +.BI "attr-dhcp-opt82-circuit-id=" attribute +Specifies the RADIUS attribute used for the Option 82 circuit ID. +.TP +.BI "attr-l4-redirect=" attribute +Specifies the RADIUS attribute that enables Layer-4 redirection. +.TP +.BI "attr-l4-redirect-table=" attribute +Specifies the RADIUS attribute containing the redirection routing-table ID. +.TP +.BI "attr-l4-redirect-ipset=" attribute +Specifies the RADIUS attribute containing the redirection ipset name. .SH [dns] .TP .BI "dns1=" x.x.x.x @@ -536,12 +672,21 @@ Specifies primary NBNS to be sent to peer. .BI "wins2=" x.x.x.x Specifies secondary NBNS to be sent to peer. .SH [ipv6-dns] +These options apply to sessions which were not assigned DNS servers of their +own. A RADIUS server may assign per session ones by returning up to 3 +DNS-Server-IPv6-Address attributes (RFC 6911) in the Access-Accept; those +replace, rather than extend, the servers configured here, for that session +only. Both the ipv6_nd module (RDNSS option of the router advertisements) and +the ipv6_dhcp module (DNS_SERVERS option) honour them. .TP .BI "dns=" IPv6_address Specifies IPv6 DNS to be sent to peer. You may specify up to 3 dns options. .TP .BI "dnssl=" name Specify DNS Search List. You may specify multiple dns and dnssl options. +.TP +.BI "lifetime=" n +Specifies the RDNSS and DNSSL lifetime advertised by the ipv6_nd module. .SH [client-ip-range] You have to explicitly specify range of ip address from which clients can connect to server in form: .br @@ -571,7 +716,7 @@ If this option is given and greater than zero then pptp module will send echo-re seconds. .TP .BI "echo-failure=" n -Specifies maximum number of echo-requests may be sent without valid echo-reply, if exceeds connection will be terminated. +Specifies maximum number of echo-requests may be sent without valid echo-reply, if exceeds connection will be terminated. Zero disables the check: echo-requests are still sent, but missing replies never terminate the connection (default 3). .TP .BI "timeout=" n Timeout waiting reply from client in seconds (default 5). @@ -591,6 +736,15 @@ sessions. Default value is 1436. Specifies max sessions time in seconds, after this time session will be terminated. .br This timeout can be redefined with radius attribute Session-Timeout. 0 value means infinite timeout. +.TP +.BI "ip-pool=" pool_name +Specifies the name of the IPv4 pool used by PPTP sessions. +.TP +.BI "ipv6-pool=" pool_name +Specifies the name of the IPv6 pool used by PPTP sessions. +.TP +.BI "ipv6-pool-delegate=" pool_name +Specifies the name of the delegated IPv6 prefix pool used by PPTP sessions. .SH [pppoe] .br Configuration of PPPoE module. @@ -634,9 +788,11 @@ By default vlan-name=%I.%N. .TP .BI "ac-name=" ac-name Specifies AC-Name tag value. If absent tag will not be sent. +The compatibility alias AC-Name=ac-name is also accepted. .TP .BI "service-name=" service-name Specifies Service-Name to respond. If absent any Service-Name is acceptable and client's Service-Name will be sent back. +The compatibility alias Service-Name=service-name is also accepted. .TP .BI "accept-any-service=" n If service-name specified still will answer with service names, but accepts any service name in PADR request. Useful @@ -648,6 +804,7 @@ Useful when needs to allow only Service-Names described in the config and also b .TP .BI "pado-delay=" delay[,delay1:count1[,delay2:count2[,...]]] Specifies delays (also in condition of connection count) to send PADO (ms). +The compatibility alias PADO-Delay=delay is also accepted. Last delay in list may be -1 which means don't accept new connections. List have to be sorted by count key. .TP @@ -691,10 +848,31 @@ as a template, i.e pppoe%d => pppoe0. Specifies max sessions time in seconds, after this time session will be terminated. .br This timeout can be redefined with radius attribute Session-Timeout. 0 value means infinite timeout. +.TP +.BI "ip-pool=" pool_name +Specifies the name of the IPv4 pool to use for allocating client addresses. +.TP +.BI "ipv6-pool=" pool_name +Specifies the name of the IPv6 pool to use for allocating client addresses. +.TP +.BI "ipv6-pool-delegate=" pool_name +Specifies the name of the IPv6 prefix delegation pool to use. +.TP +.BI "ifname-in-sid=" 0|1 +If set to 1, the interface name is included in the Session ID. +.TP +.BI "sid-uppercase=" 0|1 +If set to 1, the Session ID is generated in uppercase. +.TP +.BI "cookie-timeout=" n +Specifies the timeout in seconds for the PPPoE cookie (PADO/PADR stage). .SH [l2tp] .br Configuration of L2TP module. .TP +.BI "dictionary=" path +Specifies an alternate L2TP attribute dictionary. +.TP .BI "bind=" x.x.x.x Specifies IP address to bind. .TP @@ -787,6 +965,21 @@ as a template, i.e l2tp%d => l2tp0. Specifies max sessions time in seconds, after this time session will be terminated. .br This timeout can be redefined with radius attribute Session-Timeout. 0 value means infinite timeout. +.TP +.BI "avp_permissive=" 0|1 +If set to 1, ignore the Mandatory (M) bit in unknown Attribute-Value Pairs (AVPs) instead of terminating the connection. +.TP +.BI "dir300_quirk=" 0|1 +If set to 1, enables a workaround for D-Link DIR-300 routers that send packets with Tunnel ID 0. +.TP +.BI "ip-pool=" pool_name +Specifies the name of the IPv4 pool to use for allocating client addresses. +.TP +.BI "ipv6-pool=" pool_name +Specifies the name of the IPv6 pool to use for allocating client addresses. +.TP +.BI "ipv6-pool-delegate=" pool_name +Specifies the name of the IPv6 prefix delegation pool to use. .SH [sstp] .br Configuration of SSTP module. @@ -809,11 +1002,26 @@ Timeout waiting reply from client in seconds. Default is 60. .TP .BI "hello-interval=" n -If this option is given and greater than zero then sstp will send echo-request every +If this option is given and greater than zero then sstp will send echo-request every .B n seconds and drop connection without a reply. Default is 60. .TP +.BI "ppp-mode=" auto|seqpacket|async +Specifies transport between sstp and the kernel ppp layer. +.br +.B seqpacket +- use the ppposeq module. One datagram carries one ppp frame, so no +HDLC framing is done in userspace. +.br +.B async +- use a pty with ppp_async and frame in userspace. Works on any kernel. +.br +.B auto +- use seqpacket if the module is available, otherwise async. +.br +Default is auto. +.TP .BI "accept=" ssl,proxy Specifies incoming connection acceptance mode. .br @@ -822,6 +1030,16 @@ Specifies incoming connection acceptance mode. .br .B proxy - enable PROXY protocol 1 & 2 support. +.br +This is useful when accel-ppp is running behind a load balancer (like HAProxy or Nginx) or a reverse proxy. It allows accel-ppp to receive the original client IP address and port instead of the proxy's IP. The extracted real client IP is then used for: +.br +- \fBconnlimit\fR module checks (limiting connections per source IP). +- \fBclient-ip-range\fR verification. +- \fBCalling-Station-Id\fR attribute in RADIUS and logs. +.br +.TP +.BI "ssl=" 0|1 +Deprecated compatibility option that enables legacy SSL mode. Use accept=ssl instead. .TP .BI "ssl-protocol=" ssl2|ssl3|tls1|tls1.1|tls1.2|tls1.3 Specifies the enabled SSL/TLS protocols supported by OpenSSL library. @@ -842,14 +1060,22 @@ is greater of zero then server ciphers should be preferred over client ciphers. Default is 0. .TP .BI "ssl-pemfile=" pemfile -Specifies a file with the certificate in the PEM format for sstp server. -Certificate is also used to compute initial SHA1 and SHA256 certificate hash. +Specifies a PEM file with the server certificate. Files that contain only the +leaf certificate are still supported for backward compatibility. When the file +includes the full chain, certificates must be sorted starting with the subject's +certificate (actual server certificate), followed by intermediate CA +certificates if applicable, and ending at the highest level (root) CA. The leaf +certificate (first in the file) is used to compute the initial SHA1 and SHA256 +certificate hash. .TP .BI "ssl-keyfile=" keyfile Specifies a file with the secret key in the PEM format for sstp server. If not set, secret key will be loaded from the .BI pemfile -certificate. +parameter. +.TP +.BI "ssl-ca-file=" file +Specifies a CA bundle loaded into the SSTP TLS context. .TP .BI "cert-hash-proto=" sha1,sha256 Specifies hashing methods that can be used to compute the Compound MAC in the Crypto Binding attribute. @@ -894,6 +1120,21 @@ as a template, i.e sstp%d => sstp0. Set the maximum MTU value that can be negotiated for PPP over SSTP sessions. Default value is 1452, maximum is 4087. .TP +.BI "ip-pool=" pool_name +Specifies the name of the IPv4 pool to use for allocating client addresses. +.TP +.BI "ipv6-pool=" pool_name +Specifies the name of the IPv6 pool to use for allocating client addresses. +.TP +.BI "ipv6-pool-delegate=" pool_name +Specifies the name of the IPv6 prefix delegation pool to use. +.TP +.BI "sndbuf=" n +Specifies the TCP send buffer size (SO_SNDBUF) for the SSTP socket. +.TP +.BI "rcvbuf=" n +Specifies the TCP receive buffer size (SO_RCVBUF) for the SSTP socket. +.TP .BI "session-timeout=" n Specifies max sessions time in seconds, after this time session will be terminated. .br @@ -902,6 +1143,9 @@ This timeout can be redefined with radius attribute Session-Timeout. 0 value mea .br Configuration of RADIUS module. .TP +.BI "dictionary=" path +Specifies an alternate RADIUS dictionary. +.TP .BI "nas-identifier=" identifier Specifies value to send to RADIUS server in NAS-Identifier attribute and to be matched in DM/CoA requests. .TP @@ -950,6 +1194,11 @@ Specifies IP address, port to bind and secret for Dynamic Authorization Extensio - device name or VRF to bind the connection. By default, the device name is undefined. .br .TP +.BI "dae-allowed=" ip[,ip/cidr][,ip[/cidr]...] +Specifies allowed source IPv4 addresses or CIDR ranges for DM/CoA requests. +This setting is generally recommended to restrict DM/CoA sources, but it is not required. +.br +.TP .BI "dm_coa_secret=" secret (deprecated, use dae-server instead) Specifies secret to use in DM/CoA communication. .TP @@ -975,6 +1224,18 @@ Timeout to wait response from server (sec) .BI "max-try=" n Specifies number of tries to send Access-Request/Accounting-Request queries. .TP +.BI "req-limit=" n +Specifies the global limit of simultaneous requests per RADIUS server. Zero means unlimited. +.TP +.BI "fail-timeout=" n +Specifies how long, in seconds, an unresponsive RADIUS server remains unavailable. +.TP +.BI "max-fail=" n +Specifies the number of consecutive unanswered requests before a RADIUS server is marked unavailable. +.TP +.BI "fail-time=" n +Deprecated alias for fail-timeout. +.TP .BI "acct-timeout=" n Specifies timeout of accounting interim update. .TP @@ -1004,6 +1265,22 @@ Specifies should accel-ppp generate and send Acct-Session-Id on Access-Request p .TP .BI "nas-port-id-in-req=" 0|1 Specifies should accel-ppp send NAS-Port-Id on Access-Request and Accounting-Request packets (default 1). +.TP +.BI "bind=" x.x.x.x +Specifies the source IP address for outgoing RADIUS packets. +.TP +.BI "require-nas-identification=" 0|1 +If enabled, checks that the NAS-Identifier or NAS-IP-Address in DM/CoA requests matches the server's configuration. +.TP +.BI "blast-protection=" 0|1 +If this option is given and +.B 1 +is specified then radius module will include Message-Authenticator attribute in Access-Request packets. +.TP +.BI "framed-route-strict=" 0|1 +If enabled, only accepts Framed-Route destinations that are valid IPv4 network prefixes +for the specified mask (host bits must be zero). This enforces RFC-style network prefixes. +Default is 0 (disabled). .SH [log] .br Configuration of log and log_file modules. @@ -1017,6 +1294,12 @@ Path to file to write emergency messages. .BI "log-fail-file=" file Path to file to write authentication failed session log. .TP +.BI "log-debug=" file +Path to file to write debug messages. +.TP +.BI "color=" 0|1 +Enables ANSI color sequences in file log output. +.TP .BI "log-tcp=" x.x.x.x:port Send logs to specified host. .TP @@ -1061,10 +1344,22 @@ log all messages including debug messages .SH [log-pgsql] .br Configuration of log_pgsql module. +.br +This module is deprecated and scheduled for removal. It is built only when +accel-ppp is configured with LOG_PGSQL_DEPRECATED=TRUE. If you depend on it, +please object at https://github.com/accel-ppp/accel-ppp/issues. .TP .BI "conninfo=" conninfo Conninfo to connect to PostgreSQL server. .TP +.BI "connect-inteval=" n +Specifies the PostgreSQL reconnect interval in seconds (default 5). +The option name intentionally preserves its historical spelling. +.TP +.BI "log-query=" query +Specifies a custom parameterized INSERT query. Parameters $1 through $4 are +timestamp, username, session ID, and message respectively. +.TP .BI "log-table=" table Table to send log messages. Table must contain following field: .br @@ -1079,7 +1374,7 @@ text .br .B msg text -.SH [pppd_compat] +.SH [pppd-compat] .br Configuration of pppd_compat module. .TP @@ -1100,6 +1395,10 @@ Prefix of radattr files (for example /var/run/radattr, resulting files will be / .TP .BI "verbose=" n If specified and greater than zero pppd_module will produce verbose logging. +.TP +.BI "fork-limit=" n +Limits the number of concurrently running compatibility scripts. The default is +twice the number of online processors. .SH [chap-secrets] .br Configuration of chap-secrets module. @@ -1111,7 +1410,7 @@ Specifies address to use as local address of ppp interfaces if chap-secrets is u Specifies alternate chap-secrets file location (default is /etc/ppp/chap-secrets). .TP .BI "encrypted=" 0|1 -Specifies either chap-secrets is encrypted (read README). +Specifies whether chap-secrets is encrypted (see README.md). .TP .BI "username-hash=" hash1[,hash2] Specifies hash chain to calculate username hash. @@ -1126,16 +1425,24 @@ Configuration of ippool module. Specifies single IP address to be used as local address of ppp interfaces. .TP .BI "shuffle=" 1|0 -Specifies whether to shuffle initial address list. +Specifies whether to assign addresses in randomized order instead of sequentially. +.TP +.BI "reload-orphan=" keep|disconnect +Controls what happens on configuration reload to sessions whose assigned address +no longer belongs to any configured pool (a pool was shrunk or removed). With +.B keep +(default) such sessions keep running and their address is simply no longer tracked; +with +.B disconnect +those sessions are terminated. Reload reconciles all live sessions against the new +pools, so growing or renumbering pools no longer requires a restart. .TP .BI "gw=" range -Specifies range of local address of ppp interfaces if form: -.br -.B x.x.x.x/mask[,name=pool_name][,next=next_pool_name] -(for example 10.0.0.0/8) -.br -.B x.x.x.x-y[,name=pool_name][,next=next_pool_name] -(for example 10.0.0.1-254) +Deprecated and ignored. The per-address local gateway has long been overridden at +allocation time by +.B gw-ip-address +(or 0 for IPoE), so this option no longer has any effect. It is still accepted to +avoid breaking existing configuration files. .TP .BI "tunnel=" range Specifies range of remote address of ppp interfaces if form: @@ -1146,16 +1453,85 @@ Specifies range of remote address of ppp interfaces if form: .TP .BI "x.x.x.x/mask[,name=pool_name][,next=next_pool_name] or x.x.x.x-y[,name=pool_name][,next=next_pool_name]" Also specifies range of remote address of ppp interfaces. +.br +You may also omit the "name=" key and use a shorthand pool name after the comma, e.g. +.br +.B 10.0.0.0/24,pool1 .TP .BI "attr=" attribute Specifies which Radius attribute contains pool name. .TP .BI "vendor=" vendor If attribute is vendor-specific then specify vendor name in this option. +.PP +Note: overlapping ranges in different pools are not de-duplicated. If two pools cover the same address, that IP can be allocated from each pool independently. Use non-overlapping ranges (or a single pool with next= chaining) to avoid duplicate assignments. .SH [ipv6-dhcp] .TP +.BI "verbose=" 0|1 +Enables verbose DHCPv6 logging. +.TP +.BI "pref-lifetime=" n +Specifies the preferred address and prefix lifetime in seconds (default 604800). +.TP +.BI "valid-lifetime=" n +Specifies the valid address and prefix lifetime in seconds (default 2592000). +.TP +.BI "route-via-gw=" 0|1 +Controls whether delegated IPv6 routes are installed through the session gateway (default 1). +.TP +.BI "server-id=" x:x:x:x +Specifies the four 16-bit hexadecimal components used as the DHCPv6 server identifier. +.TP .BI "aftr-gw=" name Specifies the AFTR-Name option value to be returned on DHCPv6 replies upon request from the client. +.SH [ipv6-nd] +.br +Configuration of IPv6 Neighbor Discovery and Router Advertisements. +.TP +.BI "MaxRtrAdvInterval=" n +Maximum unsolicited Router Advertisement interval in seconds (default 600). +.TP +.BI "MinRtrAdvInterval=" n +Minimum unsolicited Router Advertisement interval in seconds (default one third of MaxRtrAdvInterval). +.TP +.BI "MaxInitialRtrAdvCount=" n +Maximum number of initial Router Advertisements (default 5). +.TP +.BI "MaxInitialRtrAdvInterval=" n +Maximum interval between initial Router Advertisements in seconds (default 3). +.TP +.BI "AdvManagedFlag=" 0|1 +Sets the Managed Address Configuration flag. By default it follows whether ipv6_dhcp is loaded. +.TP +.BI "AdvOtherConfigFlag=" 0|1 +Sets the Other Configuration flag. By default it follows whether ipv6_dhcp is loaded. +.TP +.BI "AdvLinkMTU=" n +Specifies the MTU advertised in Router Advertisements. Zero omits the MTU option. +.TP +.BI "AdvReachableTime=" n +Specifies the advertised reachable time in milliseconds. +.TP +.BI "AdvRetransTimer=" n +Specifies the advertised retransmission timer in milliseconds. +.TP +.BI "AdvCurHopLimit=" n +Specifies the advertised current hop limit (default 64). +.TP +.BI "AdvDefaultLifetime=" n +Specifies the router lifetime in seconds (default three times MaxRtrAdvInterval). +.TP +.BI "AdvValidLifetime=" n +Specifies the advertised prefix valid lifetime in seconds (default 2592000). +.TP +.BI "AdvPreferredLifetime=" n +Specifies the advertised prefix preferred lifetime in seconds (default 604800). +.TP +.BI "AdvOnLinkFlag=" 0|1 +Sets the advertised prefix on-link flag (default 1). +.TP +.BI "AdvAutonomousFlag=" 0|1 +Sets the autonomous address-configuration flag. By default it is disabled when ipv6_dhcp is loaded and enabled otherwise. .SH [ipv6-pool] .br Configuration of ipv6pool module. @@ -1187,6 +1563,89 @@ Specifies which Radius attribute contains stateful address pool name. .TP .BI "vendor=" vendor If attribute is vendor-specific then specify vendor name in this option. +.TP +.BI "reload-orphan=" keep|disconnect +Same meaning as in the +.B [ip-pool] +section, applied to IPv6 address and delegated-prefix leases on configuration reload. +.PP +Note: each pool range is materialized as a bitmap with one bit per allocatable +prefix, so the number of prefixes in a range (2^(prefix_len-mask)) is capped. Ranges +that would exceed roughly 16.7 million prefixes (prefix_len-mask > 24) are rejected at +parse time with an error rather than allocated. +.SH [metrics] +.br +Exposes daemon statistics over HTTP. Requires the +.B metrics +module to be listed in the +.B [modules] +section. The same numbers shown by +.B "accel-cmd show stat" +are returned for every GET on +.B /metrics +\&, optionally followed by per session details in the JSON format, see +.BR sessions . +Any other path responds with 404, and any method other than GET +responds with 405. +.TP +.BI "format=" prometheus|json +Selects the body format. Defaults to +.BR prometheus . +.TP +.BI "address=" host:port +IPv4 listen address. Required. Use +.BR 0.0.0.0:8080 +to listen on all interfaces. +.TP +.BI "allowed_ips=" cidr_list +Optional comma-separated list of IPv4 CIDR entries permitted to fetch +metrics. The bracketed form +.RB "\fBallowed_ips = [\(dq" "1.2.3.4/32" "\(dq, \(dq" "5.6.7.0/24" "\(dq]\fR" +and the bare form +.B "1.2.3.4/32,5.6.7.0/24" +are both accepted. A bare address without a prefix length is treated +as +.BR /32 . +When the option is missing or empty, all peers are allowed. Peers +that do not match a CIDR have their connection closed immediately +after +.BR accept (2). +.TP +.BI "read_timeout=" seconds +Maximum time a client may take to deliver a complete HTTP request. +Connections that have not produced a terminating +.B \(dq\er\en\er\en\(dq +within this deadline are closed. Defaults to 5 seconds. Set to +.B 0 +to disable the deadline. +.TP +.BI "max_clients=" n +Maximum number of in-flight HTTP clients. Excess connections are +accepted then immediately closed. Defaults to 64. Set to +.B 0 +to disable the cap. +.TP +.BI "sessions=" 0|1 +Adds a +.B session_details +array to the JSON body holding per session identity, addressing, +protocol state, interface context, uptime and traffic counters. Has no +effect with +.BR format = prometheus , +which stays aggregate only. Defaults to +.BR 0 . +.br +Two things to weigh before enabling it. Usernames, calling and called +station identifiers become readable by every peer +.B allowed_ips +permits, which is every peer when that option is unset. And the traffic +counters are the ones last sampled by accounting rather than a fresh +read of the interface, so they are only as current as the +.B radius +.B interim-interval +or the session +.B idle-timeout +makes them. .SH [connlimit] .br This module limits connection rate from single source. @@ -1205,6 +1664,9 @@ This module controls shaper. .BI "attr=" name Specifies which radius attribute contains rate information. Default - Filter-ID. .TP +.BI "vendor=" vendor +Specifies the RADIUS vendor for vendor-specific rate attributes. +.TP .BI "attr-up=" name .TP .BI "attr-down=" name @@ -1224,6 +1686,9 @@ Specifies latency (in milliseconds) parameter of tbf qdisc. .BI "mpu=" n Specifies mpu parameter of tbf qdisc and policer. .TP +.BI "mtu=" n +Specifies the MTU used in shaper calculations. +.TP .BI "r2q=" n Specifies r2q parameter of root htb qdisc. .TP @@ -1233,6 +1698,12 @@ Specifies quantum parameter of htb classes. .BI "moderate-quantum=" 1|0 If fixed quantum is not specified and this option is specified then shaper module will check for quantum value is valid (in range 1000-200000). .TP +.BI "cburst=" n +Specifies the HTB class ceil burst in bytes (default 1534). +.TP +.BI "ifb=" ifname +Specifies the IFB interface used for upstream HTB shaping. Without it, upstream HTB falls back to policing. +.TP .BI "fwmark=" n Specifies the fwmark for traffic that won't be passed through shaper. .TP @@ -1255,6 +1726,9 @@ Due to accel-ppp operates with rates in kilobit basis if you send rates in diffe .TP .BI "rate-limit=" download_speed/upload_speed Specifies, should accel-ppp set default rate-limit for clients. Clients rate-limit will be overwritten by RADIUS filter attributes or chap-secrets rate-limit params. +.TP +.BI "verbose=" 0|1 +Enables verbose shaper logging. .SH [cli] .br Configuration of the command line interface. @@ -1266,13 +1740,19 @@ If \fIn\fR = 2 then cli module will also log passed commands. .TP .BI "tcp=" host:port Defines on which IP address and port the TCP module will listen for incoming -connections. When \fIhost\fR is empty, the TCP module listens on all local -interfaces. It isn't loaded if this option isn't defined. +connections. \fIhost\fR may be an IPv4 or an IPv6 address; IPv6 addresses may +be enclosed in square brackets (e.g. \fI[::1]:2001\fR). When \fIhost\fR is +empty, the TCP module listens on all local interfaces (use \fI[::]:port\fR to +listen on all interfaces over IPv6). It isn't loaded if this option isn't +defined. .TP .BI "telnet=" host:port Defines on which IP address and port the Telnet module will listen for incoming -connections. When \fIhost\fR is empty, the Telnet module listens on all local -interfaces. It isn't loaded if this option isn't defined. +connections. \fIhost\fR may be an IPv4 or an IPv6 address; IPv6 addresses may +be enclosed in square brackets (e.g. \fI[::1]:2000\fR). When \fIhost\fR is +empty, the Telnet module listens on all local interfaces (use \fI[::]:port\fR +to listen on all interfaces over IPv6). It isn't loaded if this option isn't +defined. .TP .BI "password=" passwd Defines the password to be used by the TCP and Telnet modules for @@ -1364,3 +1844,12 @@ command (defaults to - virtual routing and forwarding .br Invalid column names are silently discarded. +.SH [snmp] +.br +Configuration of the optional net-snmp module. +.TP +.BI "master=" 0|1 +Runs as an AgentX subagent when set to 0 (the default), or as the SNMP master agent when set to 1. +.TP +.BI "agent-name=" name +Specifies the AgentX subagent name (default accel-ppp). diff --git a/accel-pppd/auth/CMakeLists.txt b/accel-pppd/auth/CMakeLists.txt index 7a1eb0b0..0270301a 100644 --- a/accel-pppd/auth/CMakeLists.txt +++ b/accel-pppd/auth/CMakeLists.txt @@ -3,7 +3,10 @@ ADD_LIBRARY(auth_chap_md5 SHARED auth_chap_md5.c) ADD_LIBRARY(auth_mschap_v1 SHARED auth_mschap_v1.c) ADD_LIBRARY(auth_mschap_v2 SHARED auth_mschap_v2.c) +TARGET_LINK_LIBRARIES(auth_chap_md5 ${crypto_lib}) +TARGET_LINK_LIBRARIES(auth_mschap_v1 ${crypto_lib}) +TARGET_LINK_LIBRARIES(auth_mschap_v2 ${crypto_lib}) + INSTALL(TARGETS auth_pap auth_chap_md5 auth_mschap_v1 auth_mschap_v2 LIBRARY DESTINATION lib${LIB_SUFFIX}/accel-ppp ) - diff --git a/accel-pppd/auth/auth_chap_md5.c b/accel-pppd/auth/auth_chap_md5.c index 3ff685e6..a5890523 100644 --- a/accel-pppd/auth/auth_chap_md5.c +++ b/accel-pppd/auth/auth_chap_md5.c @@ -7,7 +7,7 @@ #include <stdio.h> #include <arpa/inet.h> -#include "crypto.h" +#include <openssl/md5.h> #include "log.h" #include "events.h" @@ -36,7 +36,7 @@ static int conf_timeout = 5; static int conf_interval = 0; static int conf_max_failure = 3; static int conf_any_login = 0; -static const char *conf_challenge_name = "accel-ppp"; +static char *conf_challenge_name; struct chap_hdr { uint16_t proto; @@ -73,7 +73,6 @@ struct chap_auth_data { struct triton_timer_t interval; int failure; char *name; - char *mschap_error; unsigned int started:1; }; @@ -489,8 +488,9 @@ static void load_config(void) conf_any_login = atoi(opt); opt = conf_get_opt("auth", "challenge-name"); - if (opt) - conf_challenge_name = opt; + if (conf_challenge_name) + _free(conf_challenge_name); + conf_challenge_name = _strdup(opt ? opt : "accel-ppp"); } static void auth_chap_md5_init() diff --git a/accel-pppd/auth/auth_mschap_v1.c b/accel-pppd/auth/auth_mschap_v1.c index bddaabf5..909bcc5c 100644 --- a/accel-pppd/auth/auth_mschap_v1.c +++ b/accel-pppd/auth/auth_mschap_v1.c @@ -8,7 +8,9 @@ #include <byteswap.h> #include <arpa/inet.h> -#include "crypto.h" +#include <openssl/md4.h> +#include <openssl/sha.h> +#include <openssl/des.h> #include "log.h" #include "ppp.h" diff --git a/accel-pppd/auth/auth_mschap_v2.c b/accel-pppd/auth/auth_mschap_v2.c index 3e944500..440a0995 100644 --- a/accel-pppd/auth/auth_mschap_v2.c +++ b/accel-pppd/auth/auth_mschap_v2.c @@ -8,7 +8,9 @@ #include <byteswap.h> #include <arpa/inet.h> -#include "crypto.h" +#include <openssl/md4.h> +#include <openssl/sha.h> +#include <openssl/des.h> #include "log.h" #include "ppp.h" diff --git a/accel-pppd/backup/backup_file.c b/accel-pppd/backup/backup_file.c index 06dc02d4..eaaf87d2 100644 --- a/accel-pppd/backup/backup_file.c +++ b/accel-pppd/backup/backup_file.c @@ -10,11 +10,12 @@ #include <sys/uio.h> #include <sys/mman.h> +#include <openssl/md5.h> + #include "triton.h" #include "log.h" #include "ap_session.h" #include "backup.h" -#include "crypto.h" #include "memdebug.h" #define VERSION 1 @@ -95,7 +96,7 @@ static int fs_commit(struct backup_data *d) ptr = (uint8_t *)(tag + 1); *ptr = tag->id; ptr++; *ptr = tag->internal ? 1 : 0; ptr++; - *(uint16_t *)ptr = tag->size; + memcpy(ptr, &tag->size, sizeof(tag->size)); MD5_Update(&md5, tag + 1, 4 + tag->size); iov[i].iov_base = tag + 1; iov[i].iov_len = 4 + tag->size; @@ -277,14 +278,16 @@ static void restore_session(const char *fn, int internal) } if (!internal && ptr[1]) { - ptr += 4 + *(uint16_t *)(ptr + 2); + uint16_t tag_size; + memcpy(&tag_size, ptr + 2, sizeof(tag_size)); + ptr += 4 + tag_size; continue; } tag = fs_alloc_tag(d, 0); tag->id = *ptr; ptr++; tag->internal = (*ptr & 0x01) ? 1 : 0; ptr ++; - tag->size = *(uint16_t *)ptr; ptr += 2; + memcpy(&tag->size, ptr, sizeof(tag->size)); ptr += 2; tag->data = ptr; ptr += tag->size; list_add_tail(&tag->entry, &mod->tag_list); diff --git a/accel-pppd/cli/cli.c b/accel-pppd/cli/cli.c index 7d440727..6b71cb04 100644 --- a/accel-pppd/cli/cli.c +++ b/accel-pppd/cli/cli.c @@ -63,51 +63,6 @@ void __export cli_register_simple_cmd2( va_end(ap); } -void __export cli_register_regexp_cmd(struct cli_regexp_cmd_t *cmd) -{ - int err; - int erroffset; - const char *errptr; - - if (cmd->exec == NULL) { - log_emerg("cli: impossible to register regexp command" - " without an execution callback function\n"); - _exit(EXIT_FAILURE); - } - if (cmd->pattern == NULL) { - log_emerg("cli: impossible to register regexp command" - " without pattern\n"); - _exit(EXIT_FAILURE); - } - cmd->re = pcre_compile2(cmd->pattern, cmd->options, &err, - &errptr, &erroffset, NULL); - if (!cmd->re) { - log_emerg("cli: failed to compile regexp \"%s\": %s (error %i)" - " at positon %i (unprocessed characters: \"%s\")\n", - cmd->pattern, errptr, err, erroffset, - cmd->pattern + erroffset); - _exit(EXIT_FAILURE); - } - - if (cmd->h_pattern) { - cmd->h_re = pcre_compile2(cmd->h_pattern, cmd->h_options, &err, - &errptr, &erroffset, NULL); - if (!cmd->h_re) { - log_emerg("cli: failed to compile help regexp \"%s\":" - " %s (error %i) at position %i (unprocessed" - " characters: \"%s\")\n", - cmd->h_pattern, errptr, err, erroffset, - cmd->h_pattern + erroffset); - _exit(EXIT_FAILURE); - } - } else { - cmd->h_re = NULL; - cmd->h_pattern = NULL; - } - - list_add_tail(&cmd->entry, ®exp_cmd_list); -} - int __export cli_send(void *client, const char *data) { struct cli_client_t *cln = (struct cli_client_t *)client; @@ -189,13 +144,15 @@ static int cli_process_help_cmd(struct cli_client_t *cln) cmd_found = 1; list_for_each_entry(recmd, ®exp_cmd_list, entry) { + pcre2_match_data *match_data = pcre2_match_data_create(0, NULL); if (cmd[0] == '\0' - || pcre_exec(recmd->h_re, NULL, cmd, strlen(cmd), - 0, 0, NULL, 0) >= 0) { + || pcre2_match(recmd->h_re, (PCRE2_SPTR)cmd, strlen(cmd), + 0, 0, match_data, NULL) >= 0) { cmd_found = 1; if (recmd->help) recmd->help(cmd, cln); } + pcre2_match_data_free(match_data); } nb_items = split(cmd, items); @@ -230,14 +187,19 @@ static int cli_process_regexp_cmd(struct cli_client_t *cln, int *err) int res; cmd = skip_space(cmd); - list_for_each_entry(recmd, ®exp_cmd_list, entry) - if (pcre_exec(recmd->re, NULL, cmd, strlen(cmd), - 0, 0, NULL, 0) >= 0) { + list_for_each_entry(recmd, ®exp_cmd_list, entry) { + pcre2_match_data *match_data = pcre2_match_data_create(0, NULL); + if (pcre2_match(recmd->re, (PCRE2_SPTR)cmd, strlen(cmd), + 0, 0, match_data, NULL) >= 0) { found = 1; res = recmd->exec(cmd, cln); - if (res != CLI_CMD_OK) + if (res != CLI_CMD_OK) { + pcre2_match_data_free(match_data); break; + } } + pcre2_match_data_free(match_data); + } if (found) *err = res; diff --git a/accel-pppd/cli/cli.h b/accel-pppd/cli/cli.h index 6eda5d3f..3d8069b3 100644 --- a/accel-pppd/cli/cli.h +++ b/accel-pppd/cli/cli.h @@ -1,7 +1,8 @@ #ifndef __CLI_H #define __CLI_H -#include <pcre.h> +#define PCRE2_CODE_UNIT_WIDTH 8 +#include <pcre2.h> #include "list.h" @@ -23,11 +24,11 @@ struct cli_simple_cmd_t struct cli_regexp_cmd_t { struct list_head entry; - pcre *re; + pcre2_code *re; const char *pattern; int options; int (*exec)(const char *cmd, void *client); - pcre *h_re; + pcre2_code *h_re; const char *h_pattern; int h_options; int (*help)(const char *cmd, void *client); @@ -42,7 +43,6 @@ void cli_register_simple_cmd2( int hdr_len, ... ); -void cli_register_regexp_cmd(struct cli_regexp_cmd_t *cmd); void cli_show_ses_register(const char *name, const char *desc, void (*print)(struct ap_session *ses, char *buf)); int cli_send(void *client, const char *data); diff --git a/accel-pppd/cli/cli_p.h b/accel-pppd/cli/cli_p.h index 0fcba309..79208e13 100644 --- a/accel-pppd/cli/cli_p.h +++ b/accel-pppd/cli/cli_p.h @@ -2,6 +2,11 @@ #define __CLI_P_H #include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <arpa/inet.h> +#include <netinet/in.h> +#include <sys/socket.h> #include "triton.h" @@ -15,6 +20,85 @@ struct cli_client_t int cli_process_cmd(struct cli_client_t *cln); +/* Format peer address (IPv4, IPv6 or IPv4-mapped IPv6) for logging. + * buf must be at least INET6_ADDRSTRLEN bytes long. */ +static inline const char *cli_addr_str(const struct sockaddr_storage *addr, + char *buf, size_t size) +{ + const struct sockaddr_in6 *sin6 = (const struct sockaddr_in6 *)addr; + const struct sockaddr_in *sin = (const struct sockaddr_in *)addr; + + buf[0] = '\0'; + if (addr->ss_family == AF_INET6) { + if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) + inet_ntop(AF_INET, &sin6->sin6_addr.s6_addr32[3], buf, size); + else + inet_ntop(AF_INET6, &sin6->sin6_addr, buf, size); + } else + inet_ntop(AF_INET, &sin->sin_addr, buf, size); + + return buf; +} + +/* Parse "host:port", "[host]:port" or ":port" listener specification. + * str is modified in place, *host points into str afterwards (NULL for + * empty host). For unbracketed hosts the last ':' separates the port, + * so bare IPv6 addresses like "::1:2001" are accepted too. + * Returns 0 on success, -1 on invalid format. */ +static inline int cli_parse_hostport(char *str, const char **host, int *port) +{ + char *d; + + if (*str == '[') { + ++str; + d = strchr(str, ']'); + if (!d || d[1] != ':') + return -1; + *d++ = '\0'; + } else { + d = strrchr(str, ':'); + if (!d) + return -1; + } + + *d = '\0'; + *port = atoi(d + 1); + if (*port <= 0) + return -1; + + *host = *str ? str : NULL; + + return 0; +} + +/* Fill sockaddr for binding a CLI listener. host may be an IPv4 or IPv6 + * address; NULL host means any IPv4 address (use "::" for IPv6 wildcard). + * Returns 0 on success, -1 if host is not a valid address. */ +static inline int cli_bind_addr(const char *host, int port, + struct sockaddr_storage *addr, socklen_t *len) +{ + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)addr; + struct sockaddr_in *sin = (struct sockaddr_in *)addr; + + memset(addr, 0, sizeof(*addr)); + + if (host && inet_pton(AF_INET6, host, &sin6->sin6_addr) > 0) { + sin6->sin6_family = AF_INET6; + sin6->sin6_port = htons(port); + *len = sizeof(*sin6); + } else { + sin->sin_family = AF_INET; + sin->sin_port = htons(port); + if (!host) + sin->sin_addr.s_addr = htonl(INADDR_ANY); + else if (inet_pton(AF_INET, host, &sin->sin_addr) <= 0) + return -1; + *len = sizeof(*sin); + } + + return 0; +} + extern char *conf_cli_passwd; extern char *conf_cli_prompt; diff --git a/accel-pppd/cli/show_sessions.c b/accel-pppd/cli/show_sessions.c index 22f5318a..75649c4a 100644 --- a/accel-pppd/cli/show_sessions.c +++ b/accel-pppd/cli/show_sessions.c @@ -128,9 +128,9 @@ static int show_ses_exec(const char *cmd, char * const *f, int f_cnt, void *cli) struct column_t *match_key = NULL; char *match_pattern = NULL; struct column_t *order_key = NULL; - pcre *re = NULL; - const char *pcre_err; - int pcre_offset; + pcre2_code *re = NULL; + int pcre_err; + PCRE2_SIZE pcre_offset; struct column_t *column; struct col_t *col; struct row_t *row; @@ -169,9 +169,11 @@ static int show_ses_exec(const char *cmd, char * const *f, int f_cnt, void *cli) } if (match_key) { - re = pcre_compile2(match_pattern, 0, NULL, &pcre_err, &pcre_offset, NULL); + re = pcre2_compile((PCRE2_SPTR)match_pattern, PCRE2_ZERO_TERMINATED, 0, &pcre_err, &pcre_offset, NULL); if (!re) { - cli_sendv(cli, "match: %s at %i\r\n", pcre_err, pcre_offset); + PCRE2_UCHAR err_msg[64]; + pcre2_get_error_message(pcre_err, err_msg, sizeof(err_msg)); + cli_sendv(cli, "match: %s at %i\r\n", err_msg, (int)pcre_offset); return CLI_CMD_OK; } } @@ -245,6 +247,7 @@ static int show_ses_exec(const char *cmd, char * const *f, int f_cnt, void *cli) goto oom; cell->col = col; list_add_tail(&cell->entry, &row->cell_list); + cell->buf[0] = 0; col->column->print(ses, cell->buf); n = strlen(cell->buf); if (n > col->width) @@ -262,10 +265,13 @@ static int show_ses_exec(const char *cmd, char * const *f, int f_cnt, void *cli) row = list_entry(t_list.next, typeof(*row), entry); list_del(&row->entry); if (match_key) { - if (pcre_exec(re, NULL, row->match_key, strlen(row->match_key), 0, 0, NULL, 0) < 0) { + pcre2_match_data *match_data = pcre2_match_data_create(0, NULL); + if (pcre2_match(re, (PCRE2_SPTR)row->match_key, strlen(row->match_key), 0, 0, match_data, NULL) < 0) { free_row(row); + pcre2_match_data_free(match_data); continue; } + pcre2_match_data_free(match_data); } if (order_key) insert_row(&r_list, row); @@ -362,7 +368,7 @@ out: } if (re) - pcre_free(re); + pcre2_code_free(re); return CLI_CMD_OK; diff --git a/accel-pppd/cli/std_cmd.c b/accel-pppd/cli/std_cmd.c index fc073526..1d15a28c 100644 --- a/accel-pppd/cli/std_cmd.c +++ b/accel-pppd/cli/std_cmd.c @@ -5,6 +5,7 @@ #include <signal.h> #include <malloc.h> #include <arpa/inet.h> +#include <inttypes.h> #include "triton.h" #include "events.h" @@ -29,6 +30,9 @@ static int show_stat_exec(const char *cmd, char * const *fields, int fields_cnt, #ifdef MEMDEBUG struct mallinfo mi = mallinfo(); #endif + struct triton_stat_t stat; + + triton_stat_get(&stat); sprintf(statm_fname, "/proc/%i/statm", getpid()); f = fopen(statm_fname, "r"); @@ -38,14 +42,14 @@ static int show_stat_exec(const char *cmd, char * const *fields, int fields_cnt, } clock_gettime(CLOCK_MONOTONIC, &ts); - dt = ts.tv_sec - triton_stat.start_time; + dt = ts.tv_sec - stat.start_time; day = dt / (60 * 60 * 24); dt %= 60 * 60 * 24; hour = dt / (60 * 60); dt %= 60 * 60; cli_sendv(client, "uptime: %i.%02i:%02lu:%02lu\r\n", day, hour, dt / 60, dt % 60); - cli_sendv(client, "cpu: %i%%\r\n", triton_stat.cpu); + cli_sendv(client, "cpu: %i%%\r\n", stat.cpu); #ifdef MEMDEBUG cli_send(client, "memory:\r\n"); cli_sendv(client, " rss/virt: %lu/%lu kB\r\n", vmrss * page_size_kb, vmsize * page_size_kb); @@ -57,23 +61,29 @@ static int show_stat_exec(const char *cmd, char * const *fields, int fields_cnt, cli_sendv(client, "mem(rss/virt): %lu/%lu kB\r\n", vmrss * page_size_kb, vmsize * page_size_kb); #endif cli_send(client, "core:\r\n"); - cli_sendv(client, " mempool_allocated: %u\r\n", triton_stat.mempool_allocated); - cli_sendv(client, " mempool_available: %u\r\n", triton_stat.mempool_available); - cli_sendv(client, " thread_count: %u\r\n", triton_stat.thread_count); - cli_sendv(client, " thread_active: %u\r\n", triton_stat.thread_active); - cli_sendv(client, " context_count: %u\r\n", triton_stat.context_count); - cli_sendv(client, " context_sleeping: %u\r\n", triton_stat.context_sleeping); - cli_sendv(client, " context_pending: %u\r\n", triton_stat.context_pending); - cli_sendv(client, " md_handler_count: %u\r\n", triton_stat.md_handler_count); - cli_sendv(client, " md_handler_pending: %u\r\n", triton_stat.md_handler_pending); - cli_sendv(client, " timer_count: %u\r\n", triton_stat.timer_count); - cli_sendv(client, " timer_pending: %u\r\n", triton_stat.timer_pending); + cli_sendv(client, " mempool_allocated: %" PRIu64 "\r\n", stat.mempool_allocated); + cli_sendv(client, " mempool_available: %" PRIu64 "\r\n", stat.mempool_available); + cli_sendv(client, " thread_count: %u\r\n", stat.thread_count); + cli_sendv(client, " thread_active: %u\r\n", stat.thread_active); + cli_sendv(client, " context_count: %u\r\n", stat.context_count); + cli_sendv(client, " context_sleeping: %u\r\n", stat.context_sleeping); + cli_sendv(client, " context_pending: %u\r\n", stat.context_pending); + cli_sendv(client, " md_handler_count: %u\r\n", stat.md_handler_count); + cli_sendv(client, " md_handler_pending: %u\r\n", stat.md_handler_pending); + cli_sendv(client, " timer_count: %u\r\n", stat.timer_count); + cli_sendv(client, " timer_pending: %u\r\n", stat.timer_pending); //=========== - cli_send(client, "sessions:\r\n"); - cli_sendv(client, " starting: %u\r\n", ap_session_stat.starting); - cli_sendv(client, " active: %u\r\n", ap_session_stat.active); - cli_sendv(client, " finishing: %u\r\n", ap_session_stat.finishing); + { + struct ap_session_stat ses_stat; + + ap_session_stat_get(&ses_stat); + + cli_send(client, "sessions:\r\n"); + cli_sendv(client, " starting: %u\r\n", ses_stat.starting); + cli_sendv(client, " active: %u\r\n", ses_stat.active); + cli_sendv(client, " finishing: %u\r\n", ses_stat.finishing); + } return CLI_CMD_OK; } @@ -123,9 +133,9 @@ static int terminate_exec1(char * const *f, int f_cnt, void *cli) { struct ap_session *ses; int hard = 0; - pcre *re; - const char *pcre_err; - int pcre_offset; + pcre2_code *re; + int pcre_err; + PCRE2_SIZE pcre_offset; if (f_cnt == 5) { if (!strcmp(f[4], "hard")) @@ -135,9 +145,11 @@ static int terminate_exec1(char * const *f, int f_cnt, void *cli) } else if (f_cnt != 4) return CLI_CMD_SYNTAX; - re = pcre_compile2(f[3], 0, NULL, &pcre_err, &pcre_offset, NULL); + re = pcre2_compile((PCRE2_SPTR)f[3], PCRE2_ZERO_TERMINATED, 0, &pcre_err, &pcre_offset, NULL); if (!re) { - cli_sendv(cli, "match: %s at %i\r\n", pcre_err, pcre_offset); + PCRE2_UCHAR err_msg[64]; + pcre2_get_error_message(pcre_err, err_msg, sizeof(err_msg)); + cli_sendv(cli, "match: %s at %i\r\n", err_msg, (int)pcre_offset); return CLI_CMD_OK; } @@ -145,8 +157,12 @@ static int terminate_exec1(char * const *f, int f_cnt, void *cli) list_for_each_entry(ses, &ses_list, entry) { if (!ses->username) continue; - if (pcre_exec(re, NULL, ses->username, strlen(ses->username), 0, 0, NULL, 0) < 0) + pcre2_match_data *match_data = pcre2_match_data_create(0, NULL); + if (pcre2_match(re, (PCRE2_SPTR)ses->username, strlen(ses->username), 0, 0, match_data, NULL) < 0) { + pcre2_match_data_free(match_data); continue; + } + pcre2_match_data_free(match_data); if (hard) triton_context_call(ses->ctrl->ctx, (triton_event_func)__terminate_hard, ses); else @@ -154,7 +170,7 @@ static int terminate_exec1(char * const *f, int f_cnt, void *cli) } pthread_rwlock_unlock(&ses_lock); - pcre_free(re); + pcre2_code_free(re); return CLI_CMD_OK; } @@ -324,26 +340,41 @@ static int shutdown_exec(const char *cmd, char * const *f, int f_cnt, void *cli) } //========================== -static int conf_reload_res; -static struct triton_context_t *conf_reload_ctx; -static void conf_reload_notify(int r) +struct conf_reload_req { + struct triton_context_t *ctx; + int res; +}; +static void conf_reload_notify(int r, void *arg) { + struct conf_reload_req *req = arg; + if (!r) triton_event_fire(EV_CONFIG_RELOAD, NULL); - conf_reload_res = r; - triton_context_wakeup(conf_reload_ctx); + req->res = r; + triton_context_wakeup(req->ctx); } static int reload_exec(const char *cmd, char * const *f, int f_cnt, void *cli) { - if (f_cnt == 1) { - conf_reload_ctx = triton_context_self(); - triton_conf_reload(conf_reload_notify); - triton_context_schedule(); - if (conf_reload_res) - cli_send(cli, "failed\r\n"); - return CLI_CMD_OK; - } else + struct conf_reload_req *req; + + if (f_cnt != 1) return CLI_CMD_SYNTAX; + + /* heap-allocated: triton_context_schedule() can migrate this + * context to another worker thread's stack before notify runs */ + req = _malloc(sizeof(*req)); + req->ctx = triton_context_self(); + + if (triton_conf_reload(conf_reload_notify, req)) { + _free(req); + cli_send(cli, "reload is already in progress\r\n"); + return CLI_CMD_OK; + } + triton_context_schedule(); + if (req->res) + cli_send(cli, "failed\r\n"); + _free(req); + return CLI_CMD_OK; } static void reload_help(char * const *fields, int fields_cnt, void *client) diff --git a/accel-pppd/cli/tcp.c b/accel-pppd/cli/tcp.c index 8952661a..beba16ca 100644 --- a/accel-pppd/cli/tcp.c +++ b/accel-pppd/cli/tcp.c @@ -24,7 +24,7 @@ struct tcp_client_t { struct cli_client_t cli_client; struct list_head entry; struct triton_md_handler_t hnd; - struct sockaddr_in addr; + struct sockaddr_storage addr; struct list_head xmit_queue; struct buffer_t *xmit_buf; uint8_t *cmdline; @@ -174,8 +174,10 @@ static int cln_read(struct triton_md_handler_t *h) goto disconn_hard; cln->auth = 1; } else { - if (conf_verbose == 2) - log_info2("cli: %s: %s\n", inet_ntoa(cln->addr.sin_addr), cln->cmdline); + if (conf_verbose == 2) { + char buf[INET6_ADDRSTRLEN]; + log_info2("cli: %s: %s\n", cli_addr_str(&cln->addr, buf, sizeof(buf)), cln->cmdline); + } cli_process_cmd(&cln->cli_client); } @@ -248,12 +250,14 @@ disconn: static int serv_read(struct triton_md_handler_t *h) { - struct sockaddr_in addr; - socklen_t size = sizeof(addr); + struct sockaddr_storage addr; + socklen_t size; int sock; struct tcp_client_t *conn; + char buf[INET6_ADDRSTRLEN]; while(1) { + size = sizeof(addr); sock = accept(h->fd, (struct sockaddr *)&addr, &size); if (sock < 0) { if (errno == EAGAIN) @@ -263,7 +267,7 @@ static int serv_read(struct triton_md_handler_t *h) } if (conf_verbose) - log_info2("cli: tcp: new connection from %s\n", inet_ntoa(addr.sin_addr)); + log_info2("cli: tcp: new connection from %s\n", cli_addr_str(&addr, buf, sizeof(buf))); if (fcntl(sock, F_SETFL, O_NONBLOCK)) { log_error("cli: tcp: failed to set nonblocking mode: %s, closing connection...\n", strerror(errno)); @@ -320,9 +324,16 @@ static struct triton_md_handler_t serv_hnd = { static void start_server(const char *host, int port) { - struct sockaddr_in addr; + struct sockaddr_storage addr; + socklen_t addrlen; + int f = 1; + + if (cli_bind_addr(host, port, &addr, &addrlen) < 0) { + log_emerg("cli: tcp: invalid address '%s'\n", host); + return; + } - serv_hnd.fd = socket(PF_INET, SOCK_STREAM, 0); + serv_hnd.fd = socket(addr.ss_family, SOCK_STREAM, 0); if (serv_hnd.fd < 0) { log_emerg("cli: tcp: failed to create server socket: %s\n", strerror(errno)); return; @@ -330,16 +341,8 @@ static void start_server(const char *host, int port) fcntl(serv_hnd.fd, F_SETFD, fcntl(serv_hnd.fd, F_GETFD) | FD_CLOEXEC); - memset(&addr, 0, sizeof(addr)); - addr.sin_family = AF_INET; - addr.sin_port = htons(port); - if (host) - addr.sin_addr.s_addr = inet_addr(host); - else - addr.sin_addr.s_addr = htonl(INADDR_ANY); - - setsockopt(serv_hnd.fd, SOL_SOCKET, SO_REUSEADDR, &serv_hnd.fd, 4); - if (bind (serv_hnd.fd, (struct sockaddr *) &addr, sizeof (addr)) < 0) { + setsockopt(serv_hnd.fd, SOL_SOCKET, SO_REUSEADDR, &f, sizeof(f)); + if (bind (serv_hnd.fd, (struct sockaddr *) &addr, addrlen) < 0) { log_emerg("cli: tcp: failed to bind socket: %s\n", strerror(errno)); close(serv_hnd.fd); return; @@ -378,7 +381,8 @@ static void load_config(void) static void init(void) { const char *opt; - char *host, *d; + const char *addr; + char *host; int port; opt = conf_get_opt("cli", "tcp"); @@ -386,20 +390,14 @@ static void init(void) return; host = strdup(opt); - d = strstr(host, ":"); - if (!d) - goto err_fmt; - - *d = 0; - port = atoi(d + 1); - if (port <= 0) + if (cli_parse_hostport(host, &addr, &port) < 0) goto err_fmt; load_config(); temp_buf = malloc(RECV_BUF_SIZE); - start_server(host, port); + start_server(addr, port); triton_event_register_handler(EV_CONFIG_RELOAD, (triton_event_func)load_config); diff --git a/accel-pppd/cli/telnet.c b/accel-pppd/cli/telnet.c index 7d5009c2..88a9d8b0 100644 --- a/accel-pppd/cli/telnet.c +++ b/accel-pppd/cli/telnet.c @@ -37,7 +37,7 @@ struct telnet_client_t { struct cli_client_t cli_client; struct list_head entry; struct triton_md_handler_t hnd; - struct sockaddr_in addr; + struct sockaddr_storage addr; struct list_head xmit_queue; struct buffer_t *xmit_buf; int xmit_pos; @@ -305,8 +305,10 @@ static int telnet_input_char(struct telnet_client_t *cln, uint8_t c) list_add(&b->entry, cln->history.next); cln->history_pos = cln->history.next; - if (conf_verbose == 2) - log_info2("cli: %s: %s\n", inet_ntoa(cln->addr.sin_addr), cln->cmdline); + if (conf_verbose == 2) { + char abuf[INET6_ADDRSTRLEN]; + log_info2("cli: %s: %s\n", cli_addr_str(&cln->addr, abuf, sizeof(abuf)), cln->cmdline); + } if (cli_process_cmd(&cln->cli_client)) return -1; @@ -556,13 +558,15 @@ disconn: static int serv_read(struct triton_md_handler_t *h) { - struct sockaddr_in addr; - socklen_t size = sizeof(addr); + struct sockaddr_storage addr; + socklen_t size; int sock; struct telnet_client_t *conn; struct buffer_t *b, *b2; + char abuf[INET6_ADDRSTRLEN]; while(1) { + size = sizeof(addr); sock = accept(h->fd, (struct sockaddr *)&addr, &size); if (sock < 0) { if (errno == EAGAIN) @@ -572,7 +576,7 @@ static int serv_read(struct triton_md_handler_t *h) } if (conf_verbose) - log_info2("cli: telnet: new connection from %s\n", inet_ntoa(addr.sin_addr)); + log_info2("cli: telnet: new connection from %s\n", cli_addr_str(&addr, abuf, sizeof(abuf))); fcntl(sock, F_SETFL, O_NONBLOCK); fcntl(sock, F_SETFD, fcntl(sock, F_GETFD) | FD_CLOEXEC); @@ -657,9 +661,16 @@ static struct triton_md_handler_t serv_hnd = { static void start_server(const char *host, int port) { - struct sockaddr_in addr; + struct sockaddr_storage addr; + socklen_t addrlen; + int f = 1; + + if (cli_bind_addr(host, port, &addr, &addrlen) < 0) { + log_emerg("cli: telnet: invalid address '%s'\n", host); + return; + } - serv_hnd.fd = socket(PF_INET, SOCK_STREAM, 0); + serv_hnd.fd = socket(addr.ss_family, SOCK_STREAM, 0); if (serv_hnd.fd < 0) { log_emerg("cli: telnet: failed to create server socket: %s\n", strerror(errno)); return; @@ -667,16 +678,8 @@ static void start_server(const char *host, int port) fcntl(serv_hnd.fd, F_SETFD, fcntl(serv_hnd.fd, F_GETFD) | FD_CLOEXEC); - memset(&addr, 0, sizeof(addr)); - addr.sin_family = AF_INET; - addr.sin_port = htons(port); - if (host) - addr.sin_addr.s_addr = inet_addr(host); - else - addr.sin_addr.s_addr = htonl(INADDR_ANY); - - setsockopt(serv_hnd.fd, SOL_SOCKET, SO_REUSEADDR, &serv_hnd.fd, 4); - if (bind (serv_hnd.fd, (struct sockaddr *) &addr, sizeof (addr)) < 0) { + setsockopt(serv_hnd.fd, SOL_SOCKET, SO_REUSEADDR, &f, sizeof(f)); + if (bind (serv_hnd.fd, (struct sockaddr *) &addr, addrlen) < 0) { log_emerg("cli: telnet: failed to bind socket: %s\n", strerror(errno)); close(serv_hnd.fd); return; @@ -755,7 +758,8 @@ static void load_config(void) static void init(void) { const char *opt; - char *host, *d; + const char *addr; + char *host; int port; opt = conf_get_opt("cli", "telnet"); @@ -763,13 +767,7 @@ static void init(void) return; host = strdup(opt); - d = strstr(host, ":"); - if (!d) - goto err_fmt; - - *d = 0; - port = atoi(d + 1); - if (port <= 0) + if (cli_parse_hostport(host, &addr, &port) < 0) goto err_fmt; opt = conf_get_opt("cli", "history-file"); @@ -783,7 +781,7 @@ static void init(void) load_history_file(); - start_server(host, port); + start_server(addr, port); atexit(save_history_file); diff --git a/accel-pppd/ctrl/ipoe/arp.c b/accel-pppd/ctrl/ipoe/arp.c index ef9d3859..a6ca5b13 100644 --- a/accel-pppd/ctrl/ipoe/arp.c +++ b/accel-pppd/ctrl/ipoe/arp.c @@ -13,10 +13,8 @@ #include <netinet/ip.h> #include <sys/socket.h> #include <sys/ioctl.h> -#ifdef HAVE_GOOD_IFARP -#include <linux/if_arp.h> -#endif -#include <linux/if_packet.h> +#include <net/if_arp.h> +#include <netpacket/packet.h> #include "list.h" #include "triton.h" diff --git a/accel-pppd/ctrl/ipoe/dhcpv4.c b/accel-pppd/ctrl/ipoe/dhcpv4.c index 466dfee6..2bd59437 100644 --- a/accel-pppd/ctrl/ipoe/dhcpv4.c +++ b/accel-pppd/ctrl/ipoe/dhcpv4.c @@ -24,15 +24,12 @@ #include "ipdb.h" #include "radius.h" #include "dhcp_attr_defs.h" +#include "utils.h" #include "dhcpv4.h" #define BUF_SIZE 4096 -#ifndef max -#define max(x,y) ((x) > (y) ? (x) : (y)) -#endif - struct dhcpv4_relay_ctx { struct list_head entry; struct triton_context_t *ctx; @@ -161,7 +158,7 @@ struct dhcpv4_serv *dhcpv4_create(struct triton_context_t *ctx, const char *ifna goto out_err; } - if (bind(sock, &addr, sizeof(addr))) { + if (bind(sock, (struct sockaddr*)&addr, sizeof(addr))) { log_error("bind: %s\n", strerror(errno)); goto out_err; } @@ -349,6 +346,12 @@ static int dhcpv4_parse_packet(struct dhcpv4_packet *pack, int len) list_add_tail(&opt->entry, &pack->options); + } + + if (dhcpv4_check_options(pack)) + return -1; + + list_for_each_entry(opt, &pack->options, entry) { if (opt->type == 53) pack->msg_type = opt->data[0]; else if (opt->type == 82) @@ -356,17 +359,14 @@ static int dhcpv4_parse_packet(struct dhcpv4_packet *pack, int len) else if (opt->type == 62) pack->client_id = opt; else if (opt->type == 50) - pack->request_ip = *(uint32_t *)opt->data; + memcpy(&pack->request_ip, opt->data, sizeof(pack->request_ip)); else if (opt->type == 54) - pack->server_id = *(uint32_t *)opt->data; + memcpy(&pack->server_id, opt->data, sizeof(pack->server_id)); } if (pack->msg_type == 0 || pack->msg_type > 8) return -1; - if (dhcpv4_check_options(pack)) - return -1; - pack->ptr = ptr; /*if (conf_verbose) { @@ -933,6 +933,7 @@ void dhcpv4_send_notify(struct dhcpv4_serv *serv, struct dhcpv4_packet *req, uns { struct dhcpv4_packet *pack = dhcpv4_packet_alloc(); uint8_t opt[8 + ETH_ALEN]; + uint32_t value; if (!pack) { log_emerg("out of memory\n"); @@ -946,8 +947,10 @@ void dhcpv4_send_notify(struct dhcpv4_serv *serv, struct dhcpv4_packet *req, uns pack->hdr->siaddr = 0; pack->hdr->giaddr = 0; - *(uint32_t *)opt = htonl(ACCEL_PPP_MAGIC); - *(uint32_t *)(opt + 4) = htonl(weight); + value = htonl(ACCEL_PPP_MAGIC); + memcpy(opt, &value, sizeof(value)); + value = htonl(weight); + memcpy(opt + sizeof(value), &value, sizeof(value)); memcpy(opt + 8, serv->hwaddr, ETH_ALEN); dhcpv4_packet_add_opt_u8(pack, 53, DHCPDISCOVER); @@ -1012,12 +1015,12 @@ struct dhcpv4_relay *dhcpv4_relay_create(const char *_addr, in_addr_t giaddr, st if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &f, sizeof(f))) log_error("dhcpv4: setsockopt(SO_REUSEADDR): %s\n", strerror(errno)); - if (bind(sock, &laddr, sizeof(laddr))) { + if (bind(sock, (struct sockaddr*)&laddr, sizeof(laddr))) { log_error("dhcpv4: relay: %s: bind: %s\n", _addr, strerror(errno)); goto out_err_unlock; } - if (connect(sock, &raddr, sizeof(raddr))) { + if (connect(sock, (struct sockaddr*)&raddr, sizeof(raddr))) { log_error("dhcpv4: relay: %s: connect: %s\n", _addr, strerror(errno)); goto out_err_unlock; } @@ -1100,8 +1103,8 @@ int dhcpv4_relay_send(struct dhcpv4_relay *relay, struct dhcpv4_packet *request, if (server_id) { opt = dhcpv4_packet_find_opt(request, 54); if (opt) { - _server_id = *(uint32_t *)opt->data; - *(uint32_t *)opt->data = server_id; + memcpy(&_server_id, opt->data, sizeof(_server_id)); + memcpy(opt->data, &server_id, sizeof(server_id)); } } @@ -1123,7 +1126,7 @@ int dhcpv4_relay_send(struct dhcpv4_relay *relay, struct dhcpv4_packet *request, request->hdr->giaddr = giaddr; if (opt) - *(uint32_t *)opt->data = _server_id; + memcpy(opt->data, &_server_id, sizeof(_server_id)); if (n != len) return -1; diff --git a/accel-pppd/ctrl/ipoe/dhcpv4_options.c b/accel-pppd/ctrl/ipoe/dhcpv4_options.c index b5f2b3bf..fe6c2a5f 100644 --- a/accel-pppd/ctrl/ipoe/dhcpv4_options.c +++ b/accel-pppd/ctrl/ipoe/dhcpv4_options.c @@ -46,7 +46,7 @@ static struct known_option options[] = { { 26, 2, 2, 2, "MTU", print_int }, { 28, 4, 4, 4, "Broadcast", print_ip }, { 33, 8, 255, 8, "Route", print_route }, - { 42, 4, 4, 4, "NTP", print_ip }, + { 42, 4, 255, 4, "NTP", print_ip }, { 43, 1, 255, 1, "Vendor-Specific", print_hex }, { 50, 4, 4, 4, "Request-IP", print_ip }, { 51, 4, 4, 4, "Lease-Time", print_uint }, @@ -113,18 +113,28 @@ void dhcpv4_print_options(struct dhcpv4_packet *pack, void (*print)(const char * static void print_int(const struct dhcpv4_option *opt, int elem_size, void (*print)(const char *fmt, ...)) { - if (opt->len == 2) - print("%i", ntohs(*(int16_t *)(opt->data))); - else - print("%i", ntohl(*(int32_t *)(opt->data))); + if (opt->len == 2) { + int16_t val; + memcpy(&val, opt->data, sizeof(val)); + print("%i", ntohs(val)); + } else { + int32_t val; + memcpy(&val, opt->data, sizeof(val)); + print("%i", ntohl(val)); + } } static void print_uint(const struct dhcpv4_option *opt, int elem_size, void (*print)(const char *fmt, ...)) { - if (opt->len == 2) - print("%u", ntohs(*(uint16_t *)(opt->data))); - else - print("%u", ntohl(*(uint32_t *)(opt->data))); + if (opt->len == 2) { + uint16_t val; + memcpy(&val, opt->data, sizeof(val)); + print("%u", ntohs(val)); + } else { + uint32_t val; + memcpy(&val, opt->data, sizeof(val)); + print("%u", ntohl(val)); + } } static void print_ip(const struct dhcpv4_option *opt, int elem_size, void (*print)(const char *fmt, ...)) @@ -133,7 +143,8 @@ static void print_ip(const struct dhcpv4_option *opt, int elem_size, void (*prin uint32_t ip; for (i = 0; i < n; i++) { - ip = ntohl(*(uint32_t *)(opt->data + i*elem_size)); + memcpy(&ip, opt->data + i*elem_size, sizeof(ip)); + ip = ntohl(ip); if (i) print(","); @@ -170,8 +181,10 @@ static void print_route(const struct dhcpv4_option *opt, int elem_size, void (*p uint32_t ip, gw; for (i = 0; i < n; i++) { - ip = ntohl(*(uint32_t *)(opt->data + i*8)); - gw = ntohl(*(uint32_t *)(opt->data + i*8 + 4)); + memcpy(&ip, opt->data + i*8, sizeof(ip)); + memcpy(&gw, opt->data + i*8 + 4, sizeof(gw)); + ip = ntohl(ip); + gw = ntohl(gw); if (i) print(","); @@ -192,7 +205,10 @@ static void print_message_type(const struct dhcpv4_option *opt, int elem_size, v { const char *msg_name[] = {"", "Discover", "Offer", "Request", "Decline", "Ack", "Nak", "Release", "Inform"}; - print("%s", msg_name[opt->data[0]]); + if (opt->data[0] < sizeof(msg_name) / sizeof(msg_name[0])) + print("%s", msg_name[opt->data[0]]); + else + print("%u", opt->data[0]); } static void print_request_list(const struct dhcpv4_option *opt, int elem_size, void (*print)(const char *fmt, ...)) @@ -219,14 +235,19 @@ static void print_relay_agent(const struct dhcpv4_option *opt, int elem_size, vo int type, len; while (ptr < endptr) { + if (endptr - ptr < 2) { + print("invalid"); + return; + } + if (ptr != opt->data) print(" "); type = *ptr++; len = *ptr++; - /*if (ptr + len > endptr) { + if (endptr - ptr < len) { print(" invalid"); return; - }*/ + } if (type == 1) print("{Agent-Circuit-ID "); else if (type == 2) @@ -252,7 +273,9 @@ static void print_classless_route(const struct dhcpv4_option *opt, int elem_size { const uint8_t *ptr = opt->data; const uint8_t *endptr = ptr + opt->len; - int mask, i, mask1 = 0; + unsigned int prefix_len, i; + int mask; + uint32_t mask1; uint32_t ip; uint32_t gw; @@ -261,20 +284,23 @@ static void print_classless_route(const struct dhcpv4_option *opt, int elem_size print(","); mask = *ptr++; - ip = ntohl(*(uint32_t *)ptr); - for (i = 0; i < mask; i++) - mask1 |= (1 << (32 - i)); + if (mask > 32) + return; + + prefix_len = (mask + 7) / 8; + if ((size_t)(endptr - ptr) < prefix_len + sizeof(gw)) + return; + + ip = 0; + for (i = 0; i < prefix_len; i++) + ip |= (uint32_t)ptr[i] << (24 - i * 8); + mask1 = mask ? UINT32_MAX << (32 - mask) : 0; ip &= mask1; - if (mask <= 8) - ptr++; - else if (mask <= 16) - ptr += 2; - else if (mask <= 24) - ptr += 3; - else - ptr += 4; - gw = ntohl(*(uint32_t *)ptr); - ptr += 4; + ptr += prefix_len; + + memcpy(&gw, ptr, sizeof(gw)); + gw = ntohl(gw); + ptr += sizeof(gw); print("%i.%i.%i.%i/%i via %i.%i.%i.%i", (ip >> 24) & 0xff, diff --git a/accel-pppd/ctrl/ipoe/ipoe.c b/accel-pppd/ctrl/ipoe/ipoe.c index 95ff8568..e7b1320f 100644 --- a/accel-pppd/ctrl/ipoe/ipoe.c +++ b/accel-pppd/ctrl/ipoe/ipoe.c @@ -14,13 +14,9 @@ #include <netinet/ip.h> #include <sys/socket.h> #include <sys/ioctl.h> -#include <linux/if.h> -#ifdef HAVE_GOOD_IFARP -#include <linux/if_arp.h> -#endif -#include <linux/route.h> - -#include <pcre.h> +#include <net/if.h> +#include <net/if_arp.h> +#include <net/route.h> #include "events.h" #include "list.h" @@ -56,7 +52,7 @@ #define SESSION_TERMINATED "Session was terminated" struct iplink_arg { - pcre *re; + pcre2_code *re; const char *opt; long *arg1; }; @@ -184,9 +180,7 @@ static int conf_check_mac_change; static int conf_soft_terminate; static int conf_calling_sid = SID_MAC; -static unsigned int stat_starting; -static unsigned int stat_active; -static unsigned int stat_delayed_offer; +static struct ipoe_stat_t ipoe_stat; static mempool_t ses_pool; static mempool_t disc_item_pool; @@ -229,6 +223,39 @@ static struct ipoe_session *ipoe_session_create_up(struct ipoe_serv *serv, struc static void __terminate(struct ap_session *ses); static void ipoe_ipv6_disable(struct ipoe_serv *serv); +void __export ipoe_stat_get(struct ipoe_stat_t *stat) +{ + stat->starting = __atomic_load_n(&ipoe_stat.starting, __ATOMIC_RELAXED); + stat->active = __atomic_load_n(&ipoe_stat.active, __ATOMIC_RELAXED); + stat->delayed_offer = __atomic_load_n(&ipoe_stat.delayed_offer, __ATOMIC_RELAXED); +} + +unsigned int __export ipoe_stat_starting(void) +{ + return __atomic_load_n(&ipoe_stat.starting, __ATOMIC_RELAXED); +} + +unsigned int __export ipoe_stat_active(void) +{ + return __atomic_load_n(&ipoe_stat.active, __ATOMIC_RELAXED); +} + +static void ipoe_stat_inc(unsigned int *stat) +{ + __atomic_add_fetch(stat, 1, __ATOMIC_RELAXED); +} + +static void ipoe_stat_dec(unsigned int *stat) +{ + __atomic_sub_fetch(stat, 1, __ATOMIC_RELAXED); +} + +static void ipoe_stat_move(unsigned int *from, unsigned int *to) +{ + ipoe_stat_dec(from); + ipoe_stat_inc(to); +} + static void ipoe_ctx_switch(struct triton_context_t *ctx, void *arg) { if (arg) { @@ -745,7 +772,7 @@ static void ipoe_session_start(struct ipoe_session *ses) } } - __sync_add_and_fetch(&stat_starting, 1); + ipoe_stat_inc(&ipoe_stat.starting); assert(!ses->ses.username); @@ -756,6 +783,10 @@ static void ipoe_session_start(struct ipoe_session *ses) return; } + /* take ownership now so the string is freed by ipoe_session_free() + * even if the session terminates before auth_result() consumes it */ + ses->username = username; + ses->ses.unit_idx = ses->serv->ifindex; triton_event_fire(EV_CTRL_STARTING, &ses->ses); @@ -766,9 +797,9 @@ static void ipoe_session_start(struct ipoe_session *ses) if (ses->serv->opt_shared && ipoe_create_interface(ses)) return; - if (conf_noauth) + if (conf_noauth) { r = PWDB_SUCCESS; - else { + } else { #ifdef RADIUS if (radius_loaded) { ses->radius.send_access_request = ipoe_rad_send_auth_request; @@ -785,7 +816,6 @@ static void ipoe_session_start(struct ipoe_session *ses) } else pass = username; - ses->username = username; r = pwdb_check(&ses->ses, (pwdb_callback)auth_result, ses, username, PPP_PAP, pass); if (r == PWDB_WAIT) @@ -1023,9 +1053,9 @@ static void __ipoe_session_activate(struct ipoe_session *ses) in_addr_t gw; iproute_get(ses->router, &gw, NULL); if (gw) - iproute_add(0, ses->siaddr, ses->yiaddr, gw, conf_proto, 32); + iproute_add(0, ses->siaddr, ses->yiaddr, gw, conf_proto, 32, NULL); else - iproute_add(0, ses->siaddr, ses->router, gw, conf_proto, 32); + iproute_add(0, ses->siaddr, ses->router, gw, conf_proto, 32, NULL); }*/ if (serv->opt_mode == MODE_L3) @@ -1061,17 +1091,16 @@ static void __ipoe_session_activate(struct ipoe_session *ses) } } - __sync_sub_and_fetch(&stat_starting, 1); - __sync_add_and_fetch(&stat_active, 1); + ipoe_stat_move(&ipoe_stat.starting, &ipoe_stat.active); ses->started = 1; ap_session_activate(&ses->ses); if (ses->ifindex == -1 && !serv->opt_ifcfg) { if (!serv->opt_ip_unnumbered) - iproute_add(serv->ifindex, ses->router, ses->yiaddr, 0, conf_proto, ses->mask, 0); + iproute_add(serv->ifindex, ses->router, ses->yiaddr, 0, conf_proto, ses->mask, 0, NULL); else - iproute_add(serv->ifindex, serv->opt_src ?: ses->router, ses->yiaddr, 0, conf_proto, 32, 0); + iproute_add(serv->ifindex, serv->opt_src ?: ses->router, ses->yiaddr, 0, conf_proto, 32, 0, NULL); } if (ses->l4_redirect) @@ -1172,7 +1201,7 @@ static void ipoe_session_started(struct ap_session *s) if (ses->ses.ipv4->peer_addr != ses->yiaddr) //ipaddr_add_peer(ses->ses.ifindex, ses->router, ses->yiaddr); // breaks quagga - iproute_add(ses->ses.ifindex, ses->router, ses->yiaddr, 0, conf_proto, 32, 0); + iproute_add(ses->ses.ifindex, ses->router, ses->yiaddr, 0, conf_proto, 32, 0, NULL); if (ses->ifindex != -1 && ses->xid) { ses->dhcpv4 = dhcpv4_create(ses->ctrl.ctx, ses->ses.ifname, ""); @@ -1187,9 +1216,9 @@ static void ipoe_session_started(struct ap_session *s) static void ipoe_session_free(struct ipoe_session *ses) { if (ses->started) - __sync_sub_and_fetch(&stat_active, 1); + ipoe_stat_dec(&ipoe_stat.active); else - __sync_sub_and_fetch(&stat_starting, 1); + ipoe_stat_dec(&ipoe_stat.starting); if (ses->timer.tpd) triton_timer_del(&ses->timer); @@ -1215,6 +1244,9 @@ static void ipoe_session_free(struct ipoe_session *ses) if (ses->l4_redirect_ipset) _free(ses->l4_redirect_ipset); + if (ses->username) + _free(ses->username); + triton_context_unregister(&ses->ctx); if (ses->data) @@ -1256,9 +1288,9 @@ static void ipoe_session_finished(struct ap_session *s) } else if (ses->started) { if (!serv->opt_ifcfg) { if (!serv->opt_ip_unnumbered) - iproute_del(serv->ifindex, ses->router, ses->yiaddr, 0, conf_proto, ses->mask, 0); + iproute_del(serv->ifindex, ses->router, ses->yiaddr, 0, conf_proto, ses->mask, 0, NULL); else - iproute_del(serv->ifindex, serv->opt_src ?: ses->router, ses->yiaddr, 0, conf_proto, 32, 0); + iproute_del(serv->ifindex, serv->opt_src ?: ses->router, ses->yiaddr, 0, conf_proto, 32, 0, NULL); } } @@ -1365,10 +1397,10 @@ static struct ipoe_session *ipoe_session_create_dhcpv4(struct ipoe_serv *serv, s if (ap_shutdown) return NULL; - if (conf_max_starting && ap_session_stat.starting >= conf_max_starting) + if (conf_max_starting && ap_session_stat_starting() >= conf_max_starting) return NULL; - if (conf_max_sessions && ap_session_stat.active + ap_session_stat.starting >= conf_max_sessions) + if (conf_max_sessions && ap_session_stat_active() + ap_session_stat_starting() >= conf_max_sessions) return NULL; ses = ipoe_session_alloc(serv->ifname); @@ -1635,7 +1667,7 @@ static void ipoe_serv_disc_timer(struct triton_timer_t *t) list_del(&d->entry); mempool_free(d); - __sync_sub_and_fetch(&stat_delayed_offer, 1); + ipoe_stat_dec(&ipoe_stat.delayed_offer); } while (!list_empty(&serv->arp_list)) { @@ -1654,7 +1686,7 @@ static void ipoe_serv_disc_timer(struct triton_timer_t *t) list_del(&d->entry); mempool_free(d); - __sync_sub_and_fetch(&stat_delayed_offer, 1); + ipoe_stat_dec(&ipoe_stat.delayed_offer); } if (list_empty(&serv->disc_list) && list_empty(&serv->arp_list)) @@ -1675,7 +1707,7 @@ static void ipoe_serv_add_disc_arp(struct ipoe_serv *serv, struct _arphdr *arph, if (!d) return; - __sync_add_and_fetch(&stat_delayed_offer, 1); + ipoe_stat_inc(&ipoe_stat.delayed_offer); memcpy(&d->arph, arph, sizeof(*arph)); clock_gettime(CLOCK_MONOTONIC, &d->ts); @@ -1695,7 +1727,7 @@ static void ipoe_serv_add_disc(struct ipoe_serv *serv, struct dhcpv4_packet *pac if (!d) return; - __sync_add_and_fetch(&stat_delayed_offer, 1); + ipoe_stat_inc(&ipoe_stat.delayed_offer); dhcpv4_packet_ref(pack); d->pack = pack; @@ -1724,7 +1756,7 @@ static int ipoe_serv_check_disc(struct ipoe_serv *serv, struct dhcpv4_packet *pa dhcpv4_packet_free(d->pack); mempool_free(d); - __sync_sub_and_fetch(&stat_delayed_offer, 1); + ipoe_stat_dec(&ipoe_stat.delayed_offer); return 1; } @@ -1803,6 +1835,7 @@ static int check_notify(struct ipoe_serv *serv, struct dhcpv4_packet *pack) struct dhcpv4_option *opt = dhcpv4_packet_find_opt(pack, 43); struct ipoe_session *ses; unsigned int w; + uint32_t value; if (!opt) return 0; @@ -1810,10 +1843,12 @@ static int check_notify(struct ipoe_serv *serv, struct dhcpv4_packet *pack) if (opt->len != 8 + ETH_ALEN) return 0; - if (*(uint32_t *)opt->data != htonl(ACCEL_PPP_MAGIC)) + memcpy(&value, opt->data, sizeof(value)); + if (value != htonl(ACCEL_PPP_MAGIC)) return 0; - w = htonl(*(uint32_t *)(opt->data + 4)); + memcpy(&value, opt->data + sizeof(value), sizeof(value)); + w = ntohl(value); list_for_each_entry(ses, &serv->sessions, entry) { if (ses->xid == pack->hdr->xid && memcmp(pack->hdr->chaddr, ses->hwaddr, ETH_ALEN) == 0) { @@ -1872,7 +1907,7 @@ static void __ipoe_recv_dhcpv4(struct dhcpv4_serv *dhcpv4, struct dhcpv4_packet if (!ses) goto out; - ses->weight = weight = serv->opt_weight >= 0 ? serv->sess_cnt * serv->opt_weight : (stat_active + 1) * conf_weight; + ses->weight = weight = serv->opt_weight >= 0 ? serv->sess_cnt * serv->opt_weight : (ipoe_stat_active() + 1) * conf_weight; } else { if (ses->terminate) { triton_context_call(ses->ctrl.ctx, (triton_event_func)ipoe_session_terminated, ses); @@ -1997,12 +2032,14 @@ static void ipoe_ses_recv_dhcpv4_relay(struct dhcpv4_packet *pack) { struct ipoe_session *ses = container_of(triton_context_self(), typeof(*ses), ctx); struct dhcpv4_option *opt; + uint32_t value; if (ses->dhcpv4_relay_reply) dhcpv4_packet_free(ses->dhcpv4_relay_reply); if (!ses->dhcpv4_request) { ses->dhcpv4_relay_reply = NULL; + dhcpv4_packet_free(pack); return; } @@ -2014,24 +2051,32 @@ static void ipoe_ses_recv_dhcpv4_relay(struct dhcpv4_packet *pack) } opt = dhcpv4_packet_find_opt(pack, 51); - if (opt) - ses->lease_time = ntohl(*(uint32_t *)opt->data); + if (opt) { + memcpy(&value, opt->data, sizeof(value)); + ses->lease_time = ntohl(value); + } opt = dhcpv4_packet_find_opt(pack, 58); - if (opt) - ses->renew_time = ntohl(*(uint32_t *)opt->data); + if (opt) { + memcpy(&value, opt->data, sizeof(value)); + ses->renew_time = ntohl(value); + } opt = dhcpv4_packet_find_opt(pack, 59); - if (opt) - ses->rebind_time = ntohl(*(uint32_t *)opt->data); + if (opt) { + memcpy(&value, opt->data, sizeof(value)); + ses->rebind_time = ntohl(value); + } opt = dhcpv4_packet_find_opt(pack, 1); - if (opt) - ses->mask = parse_dhcpv4_mask(ntohl(*(uint32_t *)opt->data)); + if (opt) { + memcpy(&value, opt->data, sizeof(value)); + ses->mask = parse_dhcpv4_mask(ntohl(value)); + } opt = dhcpv4_packet_find_opt(pack, 3); if (opt) - ses->router = *(uint32_t *)opt->data; + memcpy(&ses->router, opt->data, sizeof(ses->router)); if (pack->msg_type == DHCPOFFER) { if (ses->ses.state == AP_STATE_STARTING) { @@ -2110,10 +2155,10 @@ static struct ipoe_session *ipoe_session_create_up(struct ipoe_serv *serv, struc if (ap_shutdown) return NULL; - if (conf_max_starting && ap_session_stat.starting >= conf_max_starting) + if (conf_max_starting && ap_session_stat_starting() >= conf_max_starting) return NULL; - if (conf_max_sessions && ap_session_stat.active + ap_session_stat.starting >= conf_max_sessions) + if (conf_max_sessions && ap_session_stat_active() + ap_session_stat_starting() >= conf_max_sessions) return NULL; if (connlimit_loaded && connlimit_check(serv->opt_shared ? cl_key_from_ipv4(saddr) : serv->ifindex)) @@ -2330,7 +2375,7 @@ void ipoe_serv_recv_arp(struct ipoe_serv *serv, struct _arphdr *arph) list_del(&d->entry); mempool_free(d); - __sync_sub_and_fetch(&stat_delayed_offer, 1); + ipoe_stat_dec(&ipoe_stat.delayed_offer); break; } @@ -2376,7 +2421,7 @@ static void ev_radius_access_accept(struct ev_radius_t *ev) ses->siaddr = attr->val.ipaddr; break; case DHCP_Router_Address: - ses->router = *(in_addr_t *)attr->raw; + memcpy(&ses->router, attr->raw, sizeof(ses->router)); break; case DHCP_Subnet_Mask: ses->mask = ipaddr_to_prefix(attr->val.ipaddr); @@ -2627,14 +2672,14 @@ static void ipoe_serv_release(struct ipoe_serv *serv) list_del(&d->entry); dhcpv4_packet_free(d->pack); mempool_free(d); - __sync_sub_and_fetch(&stat_delayed_offer, 1); + ipoe_stat_dec(&ipoe_stat.delayed_offer); } while (!list_empty(&serv->arp_list)) { struct arp_item *d = list_entry(serv->arp_list.next, typeof(*d), entry); list_del(&d->entry); mempool_free(d); - __sync_sub_and_fetch(&stat_delayed_offer, 1); + ipoe_stat_dec(&ipoe_stat.delayed_offer); } while (!list_empty(&serv->req_list)) { @@ -2707,10 +2752,14 @@ static void l4_redirect_ctx_close(struct triton_context_t *ctx) static int show_stat_exec(const char *cmd, char * const *fields, int fields_cnt, void *client) { + struct ipoe_stat_t stat; + + ipoe_stat_get(&stat); + cli_send(client, "ipoe:\r\n"); - cli_sendv(client," starting: %u\r\n", stat_starting); - cli_sendv(client," active: %u\r\n", stat_active); - cli_sendv(client," delayed: %u\r\n", stat_delayed_offer); + cli_sendv(client," starting: %u\r\n", stat.starting); + cli_sendv(client," active: %u\r\n", stat.active); + cli_sendv(client," delayed: %u\r\n", stat.delayed_offer); return CLI_CMD_OK; } @@ -2728,12 +2777,6 @@ static void print_session_type(struct ap_session *s, char *buf) *buf = 0; } -void __export ipoe_get_stat(unsigned int **starting, unsigned int **active) -{ - *starting = &stat_starting; - *active = &stat_active; -} - static void __terminate(struct ap_session *ses) { ap_session_terminate(ses, TERM_NAS_REQUEST, 1); @@ -2771,9 +2814,10 @@ struct ipoe_serv *ipoe_find_serv(const char *ifname) static int get_offer_delay() { struct delay *r, *prev = NULL; + unsigned int active = ipoe_stat_active(); list_for_each_entry(r, &conf_offer_delay, entry) { - if (!prev || stat_active >= r->conn_cnt) { + if (!prev || active >= r->conn_cnt) { prev = r; continue; } @@ -2804,10 +2848,10 @@ void ipoe_vlan_mon_notify(int ifindex, int vid, int vlan_ifindex) struct ifreq ifr; char *ptr; int len, r, svid; - pcre *re = NULL; - const char *pcre_err; + pcre2_code *re = NULL; + int pcre_err; char *pattern; - int pcre_offset; + PCRE2_SIZE pcre_offset; char ifname[IFNAMSIZ]; if (!sect) @@ -2905,15 +2949,17 @@ void ipoe_vlan_mon_notify(int ifindex, int vid, int vlan_ifindex) memcpy(pattern, opt->val + 3, ptr - (opt->val + 3)); pattern[ptr - (opt->val + 3)] = 0; - re = pcre_compile2(pattern, 0, NULL, &pcre_err, &pcre_offset, NULL); + re = pcre2_compile((PCRE2_SPTR)pattern, PCRE2_ZERO_TERMINATED, 0, &pcre_err, &pcre_offset, NULL); _free(pattern); if (!re) continue; - r = pcre_exec(re, NULL, ifname, len, 0, 0, NULL, 0); - pcre_free(re); + pcre2_match_data *match_data = pcre2_match_data_create(0, NULL); + r = pcre2_match(re, (PCRE2_SPTR)ifname, len, 0, 0, match_data, NULL); + pcre2_match_data_free(match_data); + pcre2_code_free(re); if (r < 0) continue; @@ -2943,32 +2989,38 @@ static void ipoe_serv_timeout(struct triton_timer_t *t) static void ipoe_ipv6_enable(struct ipoe_serv *serv) { struct ifreq ifr; + uint32_t addr; strcpy(ifr.ifr_name, serv->ifname); ifr.ifr_hwaddr.sa_family = AF_UNSPEC; ifr.ifr_hwaddr.sa_data[0] = 0x33; ifr.ifr_hwaddr.sa_data[1] = 0x33; - *(uint32_t *)(ifr.ifr_hwaddr.sa_data + 2) = htonl(0x02); + addr = htonl(0x02); + memcpy(ifr.ifr_hwaddr.sa_data + 2, &addr, sizeof(addr)); ioctl(sock_fd, SIOCADDMULTI, &ifr); - *(uint32_t *)(ifr.ifr_hwaddr.sa_data + 2) = htonl(0x010002); + addr = htonl(0x010002); + memcpy(ifr.ifr_hwaddr.sa_data + 2, &addr, sizeof(addr)); ioctl(sock_fd, SIOCADDMULTI, &ifr); } static void ipoe_ipv6_disable(struct ipoe_serv *serv) { struct ifreq ifr; + uint32_t addr; strcpy(ifr.ifr_name, serv->ifname); ifr.ifr_hwaddr.sa_family = AF_UNSPEC; ifr.ifr_hwaddr.sa_data[0] = 0x33; ifr.ifr_hwaddr.sa_data[1] = 0x33; - *(uint32_t *)(ifr.ifr_hwaddr.sa_data + 2) = htonl(0x02); + addr = htonl(0x02); + memcpy(ifr.ifr_hwaddr.sa_data + 2, &addr, sizeof(addr)); ioctl(sock_fd, SIOCDELMULTI, &ifr); - *(uint32_t *)(ifr.ifr_hwaddr.sa_data + 2) = htonl(0x010002); + addr = htonl(0x010002); + memcpy(ifr.ifr_hwaddr.sa_data + 2, &addr, sizeof(addr)); ioctl(sock_fd, SIOCDELMULTI, &ifr); } @@ -3109,12 +3161,12 @@ static void add_interface(const char *ifname, int ifindex, const char *opt, int sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); - if (connect(sock, &addr, sizeof(addr))) { + if (connect(sock, (struct sockaddr*)&addr, sizeof(addr))) { log_error("dhcpv4: relay: %s: connect: %s\n", opt_relay, strerror(errno)); goto out_err; } - getsockname(sock, &addr, &len); + getsockname(sock, (struct sockaddr*)&addr, &len); opt_giaddr = addr.sin_addr.s_addr; close(sock); @@ -3358,8 +3410,12 @@ static void load_interface(const char *opt) static int __load_interface_re(int index, int flags, const char *name, int iflink, int vid, struct iplink_arg *arg) { - if (pcre_exec(arg->re, NULL, name, strlen(name), 0, 0, NULL, 0) < 0) + pcre2_match_data *match_data = pcre2_match_data_create(0, NULL); + if (pcre2_match(arg->re, (PCRE2_SPTR)name, strlen(name), 0, 0, match_data, NULL) < 0) { + pcre2_match_data_free(match_data); return 0; + } + pcre2_match_data_free(match_data); add_interface(name, index, arg->opt, iflink, vid, 0); @@ -3368,11 +3424,11 @@ static int __load_interface_re(int index, int flags, const char *name, int iflin static void load_interface_re(const char *opt) { - pcre *re = NULL; - const char *pcre_err; + pcre2_code *re = NULL; + int pcre_err; char *pattern; const char *ptr; - int pcre_offset; + PCRE2_SIZE pcre_offset; struct iplink_arg arg; struct ipoe_serv *serv; @@ -3382,10 +3438,12 @@ static void load_interface_re(const char *opt) memcpy(pattern, opt + 3, ptr - (opt + 3)); pattern[ptr - (opt + 3)] = 0; - re = pcre_compile2(pattern, 0, NULL, &pcre_err, &pcre_offset, NULL); + re = pcre2_compile((PCRE2_SPTR)pattern, PCRE2_ZERO_TERMINATED, 0, &pcre_err, &pcre_offset, NULL); if (!re) { - log_error("ipoe: '%s': %s at %i\r\n", pattern, pcre_err, pcre_offset); + PCRE2_UCHAR err_msg[64]; + pcre2_get_error_message(pcre_err, err_msg, sizeof(err_msg)); + log_error("ipoe: '%s': %s at %i\r\n", pattern, err_msg, (int)pcre_offset); return; } @@ -3398,11 +3456,13 @@ static void load_interface_re(const char *opt) if (serv->active) continue; - if (pcre_exec(re, NULL, serv->ifname, strlen(serv->ifname), 0, 0, NULL, 0) >= 0) + pcre2_match_data *match_data = pcre2_match_data_create(0, NULL); + if (pcre2_match(re, (PCRE2_SPTR)serv->ifname, strlen(serv->ifname), 0, 0, match_data, NULL) >= 0) add_interface(serv->ifname, serv->ifindex, opt, 0, 0, 0); + pcre2_match_data_free(match_data); } - pcre_free(re); + pcre2_code_free(re); _free(pattern); } @@ -3474,7 +3534,7 @@ static void load_gw_addr(struct conf_sect_t *sect) continue; } - a->mask1 = ((1 << a->mask) - 1) << (32 - a->mask); + a->mask1 = (int)(((1u << a->mask) - 1u) << (32 - a->mask)); list_add_tail(&a->entry, &conf_gw_addr); } } @@ -3538,19 +3598,6 @@ static void load_radius_attrs(void) } #endif -static void strip(char *str) -{ - char *ptr = str; - char *endptr = strchr(str, 0); - while (1) { - ptr = strchr(ptr, ' '); - if (ptr) - memmove(ptr, ptr + 1, endptr - ptr - 1); - else - break; - } -} - int parse_offer_delay(const char *str) { char *str1; @@ -3567,7 +3614,7 @@ int parse_offer_delay(const char *str) return 0; str1 = _strdup(str); - strip(str1); + u_strstrip(str1, ' '); ptr1 = str1; @@ -3583,17 +3630,23 @@ int parse_offer_delay(const char *str) memset(r, 0, sizeof(*r)); r->delay = strtol(ptr1, &endptr, 10); - if (*endptr) + if (*endptr) { + _free(r); goto out_err; + } if (list_empty(&conf_offer_delay)) r->conn_cnt = 0; else { - if (!ptr3) + if (!ptr3) { + _free(r); goto out_err; + } r->conn_cnt = strtol(ptr3 + 1, &endptr, 10); - if (*endptr) + if (*endptr) { + _free(r); goto out_err; + } } list_add_tail(&r->entry, &conf_offer_delay); @@ -3608,6 +3661,11 @@ int parse_offer_delay(const char *str) return 0; out_err: + while (!list_empty(&conf_offer_delay)) { + r = list_entry(conf_offer_delay.next, typeof(*r), entry); + list_del(&r->entry); + _free(r); + } _free(str1); log_error("ipoe: failed to parse offer-delay\n"); return -1; @@ -3670,8 +3728,12 @@ static int __load_vlan_mon_re(int index, int flags, const char *name, int iflink long mask1[4096/8/sizeof(long)]; struct ipoe_serv *serv; - if (pcre_exec(arg->re, NULL, name, strlen(name), 0, 0, NULL, 0) < 0) + pcre2_match_data *match_data = pcre2_match_data_create(0, NULL); + if (pcre2_match(arg->re, (PCRE2_SPTR)name, strlen(name), 0, 0, match_data, NULL) < 0) { + pcre2_match_data_free(match_data); return 0; + } + pcre2_match_data_free(match_data); if (!(flags & IFF_UP)) { memset(&ifr, 0, sizeof(ifr)); @@ -3701,11 +3763,11 @@ static int __load_vlan_mon_re(int index, int flags, const char *name, int iflink static void load_vlan_mon_re(const char *opt, long *mask, int len) { - pcre *re = NULL; - const char *pcre_err; + pcre2_code *re = NULL; + int pcre_err; char *pattern; const char *ptr; - int pcre_offset; + PCRE2_SIZE pcre_offset; struct iplink_arg arg; for (ptr = opt; *ptr && *ptr != ','; ptr++); @@ -3714,10 +3776,12 @@ static void load_vlan_mon_re(const char *opt, long *mask, int len) memcpy(pattern, opt + 3, ptr - (opt + 3)); pattern[ptr - (opt + 3)] = 0; - re = pcre_compile2(pattern, 0, NULL, &pcre_err, &pcre_offset, NULL); + re = pcre2_compile((PCRE2_SPTR)pattern, PCRE2_ZERO_TERMINATED, 0, &pcre_err, &pcre_offset, NULL); if (!re) { - log_error("ipoe: '%s': %s at %i\r\n", pattern, pcre_err, pcre_offset); + PCRE2_UCHAR err_msg[64]; + pcre2_get_error_message(pcre_err, err_msg, sizeof(err_msg)); + log_error("ipoe: '%s': %s at %i\r\n", pattern, err_msg, (int)pcre_offset); return; } @@ -3727,7 +3791,7 @@ static void load_vlan_mon_re(const char *opt, long *mask, int len) iplink_list((iplink_list_func)__load_vlan_mon_re, &arg); - pcre_free(re); + pcre2_code_free(re); _free(pattern); } @@ -3768,6 +3832,7 @@ static void parse_local_net(const char *opt) char str[17]; in_addr_t addr; int mask; + unsigned long val; char *endptr; struct local_net *n; @@ -3778,9 +3843,10 @@ static void parse_local_net(const char *opt) addr = inet_addr(str); if (addr == INADDR_NONE) goto out_err; - mask = strtoul(ptr + 1, &endptr, 10); - if (mask > 32) + val = strtoul(ptr + 1, &endptr, 10); + if (*endptr || val > 32) goto out_err; + mask = val; } else { addr = inet_addr(opt); if (addr == INADDR_NONE) @@ -3788,7 +3854,7 @@ static void parse_local_net(const char *opt) mask = 24; } - mask = htonl(mask ? ~0 << (32 - mask) : 0); + mask = htonl(mask ? UINT32_MAX << (32 - mask) : 0); addr = addr & mask; list_for_each_entry(n, &local_nets, entry) { diff --git a/accel-pppd/ctrl/ipoe/ipoe.h b/accel-pppd/ctrl/ipoe/ipoe.h index 116602be..2bd3a14f 100644 --- a/accel-pppd/ctrl/ipoe/ipoe.h +++ b/accel-pppd/ctrl/ipoe/ipoe.h @@ -3,7 +3,7 @@ #include <stdint.h> #include <pthread.h> -#include <linux/if.h> +#include <net/if.h> #include "triton.h" #include "ap_session.h" @@ -130,6 +130,17 @@ struct ipoe_session_info { uint32_t peer_addr; }; +struct ipoe_stat_t +{ + unsigned int starting; + unsigned int active; + unsigned int delayed_offer; +}; + +void ipoe_stat_get(struct ipoe_stat_t *stat); +unsigned int ipoe_stat_starting(void); +unsigned int ipoe_stat_active(void); + int ipoe_ipv6_nd_start(struct ipoe_serv *serv); #ifdef USE_LUA @@ -153,7 +164,8 @@ void ipoe_nl_delete_interfaces(void); int ipoe_nl_create(); void ipoe_nl_delete(int ifindex); int ipoe_nl_modify(int ifindex, uint32_t peer_addr, uint32_t addr, uint32_t gw, int link_ifindex, uint8_t *hwaddr); -void ipoe_nl_get_sessions(struct list_head *list); +int ipoe_nl_get_sessions(struct list_head *list); +int ipoe_nl_flush_sessions(void); int ipoe_nl_add_exclude(uint32_t addr, int mask); void ipoe_nl_del_exclude(uint32_t addr); int ipoe_nl_add_net(uint32_t addr, int mask); diff --git a/accel-pppd/ctrl/ipoe/ipoe_netlink.c b/accel-pppd/ctrl/ipoe/ipoe_netlink.c index e7080e92..df4dec67 100644 --- a/accel-pppd/ctrl/ipoe/ipoe_netlink.c +++ b/accel-pppd/ctrl/ipoe/ipoe_netlink.c @@ -10,7 +10,7 @@ #include <net/ethernet.h> #include <netinet/ip.h> #include <arpa/inet.h> -#include <linux/if.h> +#include <net/if.h> #include <linux/genetlink.h> #include "triton.h" @@ -46,6 +46,8 @@ int ipoe_nl_add_exclude(uint32_t addr, int mask) return -1; } + memset(&req, 0, sizeof(req)); + nlh = &req.n; nlh->nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN); nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; @@ -82,6 +84,8 @@ void ipoe_nl_del_exclude(uint32_t addr) return; } + memset(&req, 0, sizeof(req)); + nlh = &req.n; nlh->nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN); nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; @@ -114,6 +118,8 @@ int ipoe_nl_add_net(uint32_t addr, int mask) return -1; } + memset(&req, 0, sizeof(req)); + nlh = &req.n; nlh->nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN); nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; @@ -150,6 +156,8 @@ void ipoe_nl_del_net(uint32_t addr) return; } + memset(&req, 0, sizeof(req)); + nlh = &req.n; nlh->nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN); nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; @@ -181,6 +189,8 @@ void ipoe_nl_add_interface(int ifindex, uint8_t mode) return; } + memset(&req, 0, sizeof(req)); + nlh = &req.n; nlh->nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN); nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; @@ -213,6 +223,8 @@ void ipoe_nl_del_interface(int ifindex) return; } + memset(&req, 0, sizeof(req)); + nlh = &req.n; nlh->nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN); nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; @@ -253,6 +265,8 @@ int ipoe_nl_create() return -1; } + memset(&req, 0, sizeof(req)); + nlh = &req.n; nlh->nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN); nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; @@ -317,6 +331,8 @@ int ipoe_nl_modify(int ifindex, uint32_t peer_addr, uint32_t addr, uint32_t gw, return -1; } + memset(&req, 0, sizeof(req)); + nlh = &req.n; nlh->nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN); nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; @@ -394,17 +410,25 @@ static int dump_session(const struct sockaddr_nl *addr, struct nlmsghdr *n, void return 0; } -void ipoe_nl_get_sessions(struct list_head *list) +int ipoe_nl_get_sessions(struct list_head *list) { + struct rtnl_handle rth; struct nlmsghdr *nlh; struct genlmsghdr *ghdr; struct { struct nlmsghdr n; char buf[1024]; } req; + int ret; - if (rth.fd == -1) - return; + /* a private socket, so that the dump does not have to compete with + * the packet notifications delivered to the multicast one */ + if (rtnl_open_byproto(&rth, 0, NETLINK_GENERIC)) { + log_error("ipoe: cannot open generic netlink socket\n"); + return -1; + } + + memset(&req, 0, sizeof(req)); nlh = &req.n; nlh->nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN); @@ -416,11 +440,51 @@ void ipoe_nl_get_sessions(struct list_head *list) ghdr->cmd = IPOE_CMD_GET; if (rtnl_send(&rth, (char *)nlh, nlh->nlmsg_len) < 0) { - log_emerg("ipoe: failed to send dump request: %s\n", strerror(errno)); - return; + log_error("ipoe: failed to send dump request: %s\n", strerror(errno)); + rtnl_close(&rth); + return -1; } - rtnl_dump_filter(&rth, dump_session, list, NULL, NULL); + ret = rtnl_dump_filter(&rth, dump_session, list, NULL, NULL); + + rtnl_close(&rth); + + return ret; +} + +int ipoe_nl_flush_sessions(void) +{ + struct rtnl_handle rth; + struct nlmsghdr *nlh; + struct genlmsghdr *ghdr; + struct { + struct nlmsghdr n; + char buf[128]; + } req; + int ret = 0; + + if (rtnl_open_byproto(&rth, 0, NETLINK_GENERIC)) { + log_error("ipoe: cannot open generic netlink socket\n"); + return -EIO; + } + + memset(&req, 0, sizeof(req)); + + nlh = &req.n; + nlh->nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN); + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + nlh->nlmsg_type = ipoe_genl_id; + + ghdr = NLMSG_DATA(&req.n); + ghdr->cmd = IPOE_CMD_FLUSH; + + errno = 0; + if (rtnl_talk(&rth, nlh, 0, 0, nlh, NULL, NULL, 0) < 0) + ret = errno ? -errno : -EIO; + + rtnl_close(&rth); + + return ret; } void ipoe_nl_delete(int ifindex) @@ -438,6 +502,8 @@ void ipoe_nl_delete(int ifindex) return; } + memset(&req, 0, sizeof(req)); + nlh = &req.n; nlh->nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN); nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; @@ -459,7 +525,10 @@ static void delete_sessions() struct ipoe_session_info *info; LIST_HEAD(ds_list); - ipoe_nl_get_sessions(&ds_list); + + if (ipoe_nl_get_sessions(&ds_list)) + log_error("ipoe: failed to enumerate sessions left by a previous" + " instance, some of them are not removed\n"); while (!list_empty(&ds_list)) { info = list_entry(ds_list.next, typeof(*info), entry); @@ -469,6 +538,24 @@ static void delete_sessions() } } +static void flush_sessions() +{ + int r = ipoe_nl_flush_sessions(); + + if (!r) + return; + + if (r == -EOPNOTSUPP) { + log_warn("ipoe: loaded ipoe module does not support IPOE_CMD_FLUSH," + " removing sessions one by one, reload the module to fix\n"); + delete_sessions(); + return; + } + + log_error("ipoe: failed to remove sessions left by a previous instance:" + " %s\n", strerror(-r)); +} + static void ipoe_up_handler(const struct sockaddr_nl *addr, struct nlmsghdr *h) { struct rtattr *tb[PKT_ATTR_MAX + 1]; @@ -628,23 +715,32 @@ static void init(void) log_warn("failed to load ipoe module\n"); mcg_id = genl_resolve_mcg(IPOE_GENL_NAME, IPOE_GENL_MCG_PKT, &ipoe_genl_id); + + if (!ipoe_genl_id) { + log_error("ipoe: cannot resolve netlink family, state left by a" + " previous instance is not removed\n"); + return; + } + + /* Drop everything a previous instance may have left in the kernel. + * The interfaces go first: while their rx handlers are still attached + * the module keeps reporting unclassified packets, and once we join + * the multicast group that traffic competes with our own replies. */ + ipoe_nl_delete_interfaces(); + flush_sessions(); + ipoe_nl_del_exclude(0); + ipoe_nl_del_net(0); + if (mcg_id == -1) { log_warn("ipoe: unclassified packet handling is disabled\n"); - rth.fd = -1; return; } if (rtnl_open_byproto(&rth, 1 << (mcg_id - 1), NETLINK_GENERIC)) { log_error("ipoe: cannot open generic netlink socket\n"); - rth.fd = -1; return; } - delete_sessions(); - ipoe_nl_del_exclude(0); - ipoe_nl_del_net(0); - ipoe_nl_delete_interfaces(); - fcntl(rth.fd, F_SETFL, O_NONBLOCK); fcntl(rth.fd, F_SETFD, fcntl(rth.fd, F_GETFD) | FD_CLOEXEC); diff --git a/accel-pppd/ctrl/l2tp/CMakeLists.txt b/accel-pppd/ctrl/l2tp/CMakeLists.txt index 66dd3fc6..80cf453b 100644 --- a/accel-pppd/ctrl/l2tp/CMakeLists.txt +++ b/accel-pppd/ctrl/l2tp/CMakeLists.txt @@ -8,10 +8,10 @@ ADD_LIBRARY(l2tp SHARED packet.c # netlink.c ) +TARGET_LINK_LIBRARIES(l2tp ${crypto_lib}) #TARGET_LINK_LIBRARIES(l2tp nl nl-genl) INSTALL(TARGETS l2tp LIBRARY DESTINATION lib${LIB_SUFFIX}/accel-ppp) FILE(GLOB dict "${CMAKE_CURRENT_SOURCE_DIR}/dict/*") INSTALL(FILES ${dict} DESTINATION share/accel-ppp/l2tp) - diff --git a/accel-pppd/ctrl/l2tp/l2tp.c b/accel-pppd/ctrl/l2tp/l2tp.c index bb1d1699..cf0c502c 100644 --- a/accel-pppd/ctrl/l2tp/l2tp.c +++ b/accel-pppd/ctrl/l2tp/l2tp.c @@ -16,6 +16,8 @@ #include <linux/if_ether.h> #include <linux/if_pppox.h> +#include <openssl/md5.h> + #include "triton.h" #include "mempool.h" #include "log.h" @@ -24,7 +26,6 @@ #include "utils.h" #include "iprange.h" #include "cli.h" -#include "crypto.h" #include "connlimit.h" @@ -99,17 +100,22 @@ static const char *conf_ipv6_pool; static const char *conf_dpv6_pool; static const char *conf_ifname; -static unsigned int stat_conn_starting; -static unsigned int stat_conn_active; -static unsigned int stat_conn_finishing; +struct l2tp_stat_t +{ + unsigned int conn_starting; + unsigned int conn_active; + unsigned int conn_finishing; -static unsigned int stat_sess_starting; -static unsigned int stat_sess_active; -static unsigned int stat_sess_finishing; + unsigned int sess_starting; + unsigned int sess_active; + unsigned int sess_finishing; -static unsigned int stat_active; -static unsigned int stat_starting; -static unsigned int stat_finishing; + unsigned int data_starting; + unsigned int data_active; + unsigned int data_finishing; +}; + +static struct l2tp_stat_t l2tp_stat; struct l2tp_serv_t { @@ -123,6 +129,11 @@ struct l2tp_sess_t struct l2tp_conn_t *paren_conn; uint16_t sid; uint16_t peer_sid; +/* We will keep l2tp attributes Calling-Number/Called-Number and their length while the session exists */ + char *calling_num; + int calling_num_len; + char *called_num; + int called_num_len; unsigned int ref_count; int state1; @@ -199,6 +210,45 @@ static void l2tp_session_free(struct l2tp_sess_t *sess); static void l2tp_tunnel_free(struct l2tp_conn_t *conn); static void apses_stop(void *data); +static void l2tp_stat_inc(unsigned int *stat) +{ + __atomic_add_fetch(stat, 1, __ATOMIC_RELAXED); +} + +static void l2tp_stat_dec(unsigned int *stat) +{ + __atomic_sub_fetch(stat, 1, __ATOMIC_RELAXED); +} + +static void l2tp_stat_move(unsigned int *from, unsigned int *to) +{ + l2tp_stat_dec(from); + l2tp_stat_inc(to); +} + +static void l2tp_stat_get(struct l2tp_stat_t *stat) +{ + stat->conn_starting = __atomic_load_n(&l2tp_stat.conn_starting, __ATOMIC_RELAXED); + stat->conn_active = __atomic_load_n(&l2tp_stat.conn_active, __ATOMIC_RELAXED); + stat->conn_finishing = __atomic_load_n(&l2tp_stat.conn_finishing, __ATOMIC_RELAXED); + stat->sess_starting = __atomic_load_n(&l2tp_stat.sess_starting, __ATOMIC_RELAXED); + stat->sess_active = __atomic_load_n(&l2tp_stat.sess_active, __ATOMIC_RELAXED); + stat->sess_finishing = __atomic_load_n(&l2tp_stat.sess_finishing, __ATOMIC_RELAXED); + stat->data_starting = __atomic_load_n(&l2tp_stat.data_starting, __ATOMIC_RELAXED); + stat->data_active = __atomic_load_n(&l2tp_stat.data_active, __ATOMIC_RELAXED); + stat->data_finishing = __atomic_load_n(&l2tp_stat.data_finishing, __ATOMIC_RELAXED); +} + +unsigned int __export l2tp_stat_starting(void) +{ + return __atomic_load_n(&l2tp_stat.data_starting, __ATOMIC_RELAXED); +} + +unsigned int __export l2tp_stat_active(void) +{ + return __atomic_load_n(&l2tp_stat.data_active, __ATOMIC_RELAXED); +} + #define log_tunnel(log_func, conn, fmt, ...) \ do { \ @@ -848,16 +898,17 @@ out_err: return -1; } +static void l2tp_session_free_ptr(void *ptr) +{ + l2tp_session_free((struct l2tp_sess_t *) ptr); +} + static void l2tp_tunnel_free_sessions(struct l2tp_conn_t *conn) { void *sessions = conn->sessions; conn->sessions = NULL; -#ifdef HAVE_FREE_FN_T - tdestroy(sessions, (__free_fn_t)l2tp_session_free); -#else - tdestroy(sessions, (void(*)(void *))l2tp_session_free); -#endif + tdestroy(sessions, l2tp_session_free_ptr); /* Let l2tp_session_free() handle the session counter and * the reference held by the tunnel. */ @@ -870,12 +921,10 @@ static int l2tp_tunnel_disconnect(struct l2tp_conn_t *conn, case STATE_INIT: case STATE_WAIT_SCCRP: case STATE_WAIT_SCCCN: - __sync_sub_and_fetch(&stat_conn_starting, 1); - __sync_add_and_fetch(&stat_conn_finishing, 1); + l2tp_stat_move(&l2tp_stat.conn_starting, &l2tp_stat.conn_finishing); break; case STATE_ESTB: - __sync_sub_and_fetch(&stat_conn_active, 1); - __sync_add_and_fetch(&stat_conn_finishing, 1); + l2tp_stat_move(&l2tp_stat.conn_active, &l2tp_stat.conn_finishing); break; case STATE_FIN: case STATE_FIN_WAIT: @@ -955,7 +1004,7 @@ static void __tunnel_destroy(struct l2tp_conn_t *conn) mempool_free(conn); - __sync_sub_and_fetch(&stat_conn_finishing, 1); + l2tp_stat_dec(&l2tp_stat.conn_finishing); } static void tunnel_put(struct l2tp_conn_t *conn) @@ -983,12 +1032,16 @@ static void __session_destroy(struct l2tp_sess_t *sess) _free(sess->ctrl.calling_station_id); if (sess->ctrl.called_station_id) _free(sess->ctrl.called_station_id); + if (sess->calling_num) + _free(sess->calling_num); + if (sess->called_num) + _free(sess->called_num); log_session(log_info2, sess, "session destroyed\n"); mempool_free(sess); - __sync_sub_and_fetch(&stat_sess_finishing, 1); + l2tp_stat_dec(&l2tp_stat.sess_finishing); /* Now that the session is fully destroyed, * drop the reference to the tunnel. @@ -1021,15 +1074,13 @@ static void l2tp_session_free(struct l2tp_sess_t *sess) case STATE_WAIT_OCCN: log_session(log_info2, sess, "deleting session\n"); - __sync_sub_and_fetch(&stat_sess_starting, 1); - __sync_add_and_fetch(&stat_sess_finishing, 1); + l2tp_stat_move(&l2tp_stat.sess_starting, &l2tp_stat.sess_finishing); break; case STATE_ESTB: log_session(log_info2, sess, "deleting session\n"); triton_event_fire(EV_CTRL_FINISHED, &sess->ppp.ses); - __sync_sub_and_fetch(&stat_sess_active, 1); - __sync_add_and_fetch(&stat_sess_finishing, 1); + l2tp_stat_move(&l2tp_stat.sess_active, &l2tp_stat.sess_finishing); pthread_mutex_lock(&sess->apses_lock); if (sess->apses_ctx.tpd) @@ -1124,12 +1175,10 @@ static void l2tp_tunnel_free(struct l2tp_conn_t *conn) case STATE_INIT: case STATE_WAIT_SCCRP: case STATE_WAIT_SCCCN: - __sync_sub_and_fetch(&stat_conn_starting, 1); - __sync_add_and_fetch(&stat_conn_finishing, 1); + l2tp_stat_move(&l2tp_stat.conn_starting, &l2tp_stat.conn_finishing); break; case STATE_ESTB: - __sync_sub_and_fetch(&stat_conn_active, 1); - __sync_add_and_fetch(&stat_conn_finishing, 1); + l2tp_stat_move(&l2tp_stat.conn_active, &l2tp_stat.conn_finishing); break; case STATE_FIN: case STATE_FIN_WAIT: @@ -1252,7 +1301,7 @@ static void __apses_destroy(void *data) log_ppp_info2("session destroyed\n"); - __sync_sub_and_fetch(&stat_finishing, 1); + l2tp_stat_dec(&l2tp_stat.data_finishing); /* Drop reference to the L2TP session */ session_put(sess); @@ -1267,12 +1316,10 @@ static void apses_finished(struct ap_session *apses) switch (sess->apses_state) { case APSTATE_STARTING: - __sync_sub_and_fetch(&stat_starting, 1); - __sync_add_and_fetch(&stat_finishing, 1); + l2tp_stat_move(&l2tp_stat.data_starting, &l2tp_stat.data_finishing); break; case APSTATE_STARTED: - __sync_sub_and_fetch(&stat_active, 1); - __sync_add_and_fetch(&stat_finishing, 1); + l2tp_stat_move(&l2tp_stat.data_active, &l2tp_stat.data_finishing); break; case APSTATE_FINISHING: break; @@ -1313,12 +1360,10 @@ static void apses_stop(void *data) switch (sess->apses_state) { case APSTATE_INIT: case APSTATE_STARTING: - __sync_sub_and_fetch(&stat_starting, 1); - __sync_add_and_fetch(&stat_finishing, 1); + l2tp_stat_move(&l2tp_stat.data_starting, &l2tp_stat.data_finishing); break; case APSTATE_STARTED: - __sync_sub_and_fetch(&stat_active, 1); - __sync_add_and_fetch(&stat_finishing, 1); + l2tp_stat_move(&l2tp_stat.data_active, &l2tp_stat.data_finishing); break; case APSTATE_FINISHING: break; @@ -1377,8 +1422,7 @@ static void apses_started(struct ap_session *apses) return; } - __sync_sub_and_fetch(&stat_starting, 1); - __sync_add_and_fetch(&stat_active, 1); + l2tp_stat_move(&l2tp_stat.data_starting, &l2tp_stat.data_active); sess->apses_state = APSTATE_STARTED; log_ppp_info1("session started over l2tp session %hu-%hu, %hu-%hu\n", @@ -1507,7 +1551,7 @@ static struct l2tp_sess_t *l2tp_tunnel_alloc_session(struct l2tp_conn_t *conn) tunnel_hold(conn); session_hold(sess); - __sync_add_and_fetch(&stat_sess_starting, 1); + l2tp_stat_inc(&l2tp_stat.sess_starting); return sess; } @@ -1613,7 +1657,7 @@ static struct l2tp_conn_t *l2tp_tunnel_alloc(const struct sockaddr_in *peer, strerror(errno)); goto err_conn_fd; } - if (bind(conn->hnd.fd, host, sizeof(*host))) { + if (bind(conn->hnd.fd, (struct sockaddr*)host, sizeof(*host))) { log_error("l2tp: impossible to allocate new tunnel:" " bind() failed: %s\n", strerror(errno)); goto err_conn_fd; @@ -1646,7 +1690,7 @@ static struct l2tp_conn_t *l2tp_tunnel_alloc(const struct sockaddr_in *peer, goto err_conn_fd; } - if (getsockname(conn->hnd.fd, &conn->host_addr, &hostaddrlen) < 0) { + if (getsockname(conn->hnd.fd, (struct sockaddr*)&conn->host_addr, &hostaddrlen) < 0) { log_error("l2tp: impossible to allocate new tunnel:" " getsockname() failed: %s\n", strerror(errno)); goto err_conn_fd; @@ -1726,7 +1770,7 @@ static struct l2tp_conn_t *l2tp_tunnel_alloc(const struct sockaddr_in *peer, conn->peer_rcv_wnd_sz = DEFAULT_PEER_RECV_WINDOW_SIZE; tunnel_hold(conn); - __sync_add_and_fetch(&stat_conn_starting, 1); + l2tp_stat_inc(&l2tp_stat.conn_starting); return conn; @@ -1747,7 +1791,7 @@ static inline int l2tp_tunnel_update_peerport(struct l2tp_conn_t *conn, int res; conn->peer_addr.sin_port = port_nbo; - res = connect(conn->hnd.fd, &conn->peer_addr, sizeof(conn->peer_addr)); + res = connect(conn->hnd.fd, (struct sockaddr*)&conn->peer_addr, sizeof(conn->peer_addr)); if (res < 0) { log_tunnel(log_error, conn, "impossible to update peer port from %hu to %hu:" @@ -1775,25 +1819,52 @@ static int l2tp_session_start_data_channel(struct l2tp_sess_t *sess) sess->ctrl.max_mtu = conf_ppp_max_mtu; sess->ctrl.mppe = conf_mppe; - sess->ctrl.calling_station_id = _malloc(17); - if (sess->ctrl.calling_station_id == NULL) { - log_session(log_error, sess, - "impossible to start data channel:" - " allocation of calling station ID failed\n"); - goto err; + /* If l2tp calling number avp exists, we use it, otherwise we use lac ip */ + if (sess->calling_num != NULL) { + sess->ctrl.calling_station_id = _malloc(sess->calling_num_len+1); + if (sess->ctrl.calling_station_id == NULL) { + log_session(log_error, sess, + "impossible to start data channel:" + " allocation of calling station ID failed\n"); + goto err; + }else { + strcpy(sess->ctrl.calling_station_id, sess->calling_num); + } + } else { + sess->ctrl.calling_station_id = _malloc(17); + if (sess->ctrl.calling_station_id == NULL) { + log_session(log_error, sess, + "impossible to start data channel:" + " allocation of calling station ID failed\n"); + goto err; + } else { + u_inet_ntoa(sess->paren_conn->peer_addr.sin_addr.s_addr, + sess->ctrl.calling_station_id); + } } - u_inet_ntoa(sess->paren_conn->peer_addr.sin_addr.s_addr, - sess->ctrl.calling_station_id); - - sess->ctrl.called_station_id = _malloc(17); - if (sess->ctrl.called_station_id == NULL) { - log_session(log_error, sess, - "impossible to start data channel:" - " allocation of called station ID failed\n"); - goto err; + /* If l2tp called number avp exists, we use it, otherwise we use my ip */ + if (sess->called_num != NULL) { + sess->ctrl.called_station_id = _malloc(sess->called_num_len+1); + if (sess->ctrl.called_station_id == NULL) { + log_session(log_error, sess, + "impossible to start data channel:" + " allocation of called station ID failed\n"); + goto err; + } else { + strcpy(sess->ctrl.called_station_id, sess->called_num); + } + } else { + sess->ctrl.called_station_id = _malloc(17); + if (sess->ctrl.called_station_id == NULL) { + log_session(log_error, sess, + "impossible to start data channel:" + " allocation of called station ID failed\n"); + goto err; + } else { + u_inet_ntoa(sess->paren_conn->host_addr.sin_addr.s_addr, + sess->ctrl.called_station_id); + } } - u_inet_ntoa(sess->paren_conn->host_addr.sin_addr.s_addr, - sess->ctrl.called_station_id); if (conf_ip_pool) { sess->ppp.ses.ipv4_pool_name = _strdup(conf_ip_pool); @@ -1844,7 +1915,7 @@ static int l2tp_session_start_data_channel(struct l2tp_sess_t *sess) goto err_put_ctx; } - __sync_add_and_fetch(&stat_starting, 1); + l2tp_stat_inc(&l2tp_stat.data_starting); return 0; @@ -1974,8 +2045,7 @@ static int l2tp_session_connect(struct l2tp_sess_t *sess) } triton_event_fire(EV_CTRL_STARTED, &sess->ppp.ses); - __sync_sub_and_fetch(&stat_sess_starting, 1); - __sync_add_and_fetch(&stat_sess_active, 1); + l2tp_stat_move(&l2tp_stat.sess_starting, &l2tp_stat.sess_active); sess->state1 = STATE_ESTB; if (l2tp_session_start_data_channel(sess) < 0) { @@ -2053,8 +2123,7 @@ static int l2tp_tunnel_connect(struct l2tp_conn_t *conn) close(tunnel_fd); - __sync_sub_and_fetch(&stat_conn_starting, 1); - __sync_add_and_fetch(&stat_conn_active, 1); + l2tp_stat_move(&l2tp_stat.conn_starting, &l2tp_stat.conn_active); conn->state = STATE_ESTB; return 0; @@ -2684,12 +2753,10 @@ static void l2tp_tunnel_finwait(struct l2tp_conn_t *conn) switch (conn->state) { case STATE_WAIT_SCCRP: case STATE_WAIT_SCCCN: - __sync_sub_and_fetch(&stat_conn_starting, 1); - __sync_add_and_fetch(&stat_conn_finishing, 1); + l2tp_stat_move(&l2tp_stat.conn_starting, &l2tp_stat.conn_finishing); break; case STATE_ESTB: - __sync_sub_and_fetch(&stat_conn_active, 1); - __sync_add_and_fetch(&stat_conn_finishing, 1); + l2tp_stat_move(&l2tp_stat.conn_active, &l2tp_stat.conn_finishing); break; case STATE_FIN: break; @@ -2769,10 +2836,10 @@ static int l2tp_recv_SCCRQ(const struct l2tp_serv_t *serv, return 0; } - if (conf_max_starting && ap_session_stat.starting >= conf_max_starting) + if (conf_max_starting && ap_session_stat_starting() >= conf_max_starting) return 0; - if (conf_max_sessions && ap_session_stat.active + ap_session_stat.starting >= conf_max_sessions) + if (conf_max_sessions && ap_session_stat_active() + ap_session_stat_starting() >= conf_max_sessions) return 0; if (triton_module_loaded("connlimit") @@ -3299,6 +3366,10 @@ static int l2tp_recv_ICRQ(struct l2tp_conn_t *conn, uint16_t sid = 0; uint16_t res = 0; uint16_t err = 0; + uint8_t calling[L2TP_AVP_LEN_MASK] = {0}; + uint8_t called[L2TP_AVP_LEN_MASK] = {0}; + int n = 0; + int m = 0; if (conn->state != STATE_ESTB && conn->lns_mode) { log_tunnel(log_warn, conn, "discarding unexpected ICRQ\n"); @@ -3311,10 +3382,10 @@ static int l2tp_recv_ICRQ(struct l2tp_conn_t *conn, return 0; } - if (conf_max_starting && ap_session_stat.starting >= conf_max_starting) + if (conf_max_starting && ap_session_stat_starting() >= conf_max_starting) return 0; - if (conf_max_sessions && ap_session_stat.active + ap_session_stat.starting >= conf_max_sessions) + if (conf_max_sessions && ap_session_stat_active() + ap_session_stat_starting() >= conf_max_sessions) return 0; if (triton_module_loaded("connlimit") @@ -3336,7 +3407,17 @@ static int l2tp_recv_ICRQ(struct l2tp_conn_t *conn, case Call_Serial_Number: case Bearer_Type: case Calling_Number: + /* Save Calling-Number L2TP attribute locally */ + if (attr->attr->id == Calling_Number) { + n = attr->length; + memcpy(calling,attr->val.octets,n); + } case Called_Number: + /* Save Called-Number L2TP attribute locally */ + if (attr->attr->id == Called_Number) { + m = attr->length; + memcpy(called,attr->val.octets,m); + } case Sub_Address: case Physical_Channel_ID: break; @@ -3375,6 +3456,30 @@ static int l2tp_recv_ICRQ(struct l2tp_conn_t *conn, sess->peer_sid = peer_sid; sid = sess->sid; + /* Allocate memory for Calling-Number if exists, and put it to l2tp_sess_t structure */ + if (n > 0) { + sess->calling_num = _malloc(n+1); + if (sess->calling_num == NULL) { + log_tunnel(log_warn, conn, "can't allocate memory for Calling Number attribute. Will use LAC IP instead\n"); + }else{ + memcpy(sess->calling_num, calling, n); + sess->calling_num[n] = '\0'; + sess->calling_num_len = n; + } + } + + /* Allocate memory for Called-Number if exists, and put it to l2tp_sess_t structure */ + if (m > 1) { + sess->called_num = _malloc(m+1); + if (sess->called_num == NULL) { + log_tunnel(log_warn, conn, "can't allocate memory for Called Number attribute. Will use my IP instead\n"); + } else { + memcpy(sess->called_num, called, m); + sess->called_num[m] = '\0'; + sess->called_num_len = m; + } + } + if (unknown_attr) { log_tunnel(log_error, conn, "impossible to handle ICRQ:" " unknown mandatory attribute type %i," @@ -3394,8 +3499,8 @@ static int l2tp_recv_ICRQ(struct l2tp_conn_t *conn, goto out_reject; } - log_tunnel(log_info1, conn, "new session %hu-%hu created following" - " reception of ICRQ\n", sid, peer_sid); + log_tunnel(log_info1, conn, "new session %hu-%hu with calling num %s len %d, called num %s len %d created following" + " reception of ICRQ\n", sid, peer_sid, sess->calling_num, sess->calling_num_len, sess->called_num, sess->called_num_len); return 0; @@ -3617,10 +3722,10 @@ static int l2tp_recv_OCRQ(struct l2tp_conn_t *conn, return 0; } - if (conf_max_starting && ap_session_stat.starting >= conf_max_starting) + if (conf_max_starting && ap_session_stat_starting() >= conf_max_starting) return 0; - if (conf_max_sessions && ap_session_stat.active + ap_session_stat.starting >= conf_max_sessions) + if (conf_max_sessions && ap_session_stat_active() + ap_session_stat_starting() >= conf_max_sessions) return 0; if (triton_module_loaded("connlimit") @@ -4637,21 +4742,25 @@ err_fd: static int show_stat_exec(const char *cmd, char * const *fields, int fields_cnt, void *client) { + struct l2tp_stat_t stat; + + l2tp_stat_get(&stat); + cli_send(client, "l2tp:\r\n"); cli_send(client, " tunnels:\r\n"); - cli_sendv(client, " starting: %u\r\n", stat_conn_starting); - cli_sendv(client, " active: %u\r\n", stat_conn_active); - cli_sendv(client, " finishing: %u\r\n", stat_conn_finishing); + cli_sendv(client, " starting: %u\r\n", stat.conn_starting); + cli_sendv(client, " active: %u\r\n", stat.conn_active); + cli_sendv(client, " finishing: %u\r\n", stat.conn_finishing); cli_send(client, " sessions (control channels):\r\n"); - cli_sendv(client, " starting: %u\r\n", stat_sess_starting); - cli_sendv(client, " active: %u\r\n", stat_sess_active); - cli_sendv(client, " finishing: %u\r\n", stat_sess_finishing); + cli_sendv(client, " starting: %u\r\n", stat.sess_starting); + cli_sendv(client, " active: %u\r\n", stat.sess_active); + cli_sendv(client, " finishing: %u\r\n", stat.sess_finishing); cli_send(client, " sessions (data channels):\r\n"); - cli_sendv(client, " starting: %u\r\n", stat_starting); - cli_sendv(client, " active: %u\r\n", stat_active); - cli_sendv(client, " finishing: %u\r\n", stat_finishing); + cli_sendv(client, " starting: %u\r\n", stat.data_starting); + cli_sendv(client, " active: %u\r\n", stat.data_active); + cli_sendv(client, " finishing: %u\r\n", stat.data_finishing); return CLI_CMD_OK; } @@ -4857,12 +4966,6 @@ static void l2tp_create_session_help(char * const *fields, int fields_cnt, " - place new call in tunnel <tid>\r\n"); } -void __export l2tp_get_stat(unsigned int **starting, unsigned int **active) -{ - *starting = &stat_starting; - *active = &stat_active; -} - static void load_config(void) { const char *opt; diff --git a/accel-pppd/ctrl/l2tp/l2tp.h b/accel-pppd/ctrl/l2tp/l2tp.h index 76de867f..2f113a25 100644 --- a/accel-pppd/ctrl/l2tp/l2tp.h +++ b/accel-pppd/ctrl/l2tp/l2tp.h @@ -77,6 +77,9 @@ struct l2tp_packet_t extern int conf_verbose; extern int conf_avp_permissive; +unsigned int l2tp_stat_starting(void); +unsigned int l2tp_stat_active(void); + static inline int l2tp_packet_is_ZLB(const struct l2tp_packet_t *pack) { return list_empty(&pack->attrs); diff --git a/accel-pppd/ctrl/l2tp/packet.c b/accel-pppd/ctrl/l2tp/packet.c index 97e205f3..f134666d 100644 --- a/accel-pppd/ctrl/l2tp/packet.c +++ b/accel-pppd/ctrl/l2tp/packet.c @@ -8,7 +8,8 @@ #include <fcntl.h> #include <arpa/inet.h> -#include "crypto.h" +#include <openssl/md5.h> + #include "triton.h" #include "log.h" #include "mempool.h" @@ -112,42 +113,26 @@ void l2tp_packet_free(struct l2tp_packet_t *pack) static void memxor(uint8_t *dst, const uint8_t *src, size_t sz) { - const uintmax_t *umax_src = (const uintmax_t *)src; - uintmax_t *umax_dst = (uintmax_t *)dst; - size_t left = sz % sizeof(uintmax_t); size_t indx; - for (indx = 0; indx < sz / sizeof(uintmax_t); ++indx) - umax_dst[indx] ^= umax_src[indx]; - - src += sz - left; - dst += sz - left; - while (left) { - if (left >= sizeof(uint32_t)) { - *(uint32_t *)dst ^= *(uint32_t *)src; - src += sizeof(uint32_t); - dst += sizeof(uint32_t); - left -= sizeof(uint32_t); - } else if (left >= sizeof(uint16_t)) { - *(uint16_t *)dst ^= *(uint16_t *)src; - src += sizeof(uint16_t); - dst += sizeof(uint16_t); - left -= sizeof(uint16_t); - } else { - *dst ^= *src; - src += sizeof(uint8_t); - dst += sizeof(uint8_t); - left -= sizeof(uint8_t); - } - } + for (indx = 0; indx < sz; ++indx) + dst[indx] ^= src[indx]; } /* * Decipher hidden AVPs, keeping the Hidden AVP Subformat (i.e. the attribute * value is prefixed by 2 bytes indicating its length in network byte order). + * + * On success the deciphered original attribute length is stored into + * *orig_attr_len, already validated against the size of the received AVP. + * Callers must never re-read that length from the AVP body themselves: it is + * the output of the cipher, so a peer using a mismatching secret (or an + * attacker blindly injecting hidden AVPs) makes it an essentially random + * 16 bits value. */ static int decode_avp(struct l2tp_avp_t *avp, const struct l2tp_attr_t *RV, - const char *secret, size_t secret_len) + const char *secret, size_t secret_len, + uint16_t *orig_attr_len_out) { MD5_CTX md5_ctx; uint8_t md5[MD5_DIGEST_LENGTH]; @@ -161,7 +146,7 @@ static int decode_avp(struct l2tp_avp_t *avp, const struct l2tp_attr_t *RV, uint16_t last_block_len; avp_len = avp->flags & L2TP_AVP_LEN_MASK; - if (avp_len < sizeof(struct l2tp_avp_t) + 2) { + if (avp_len < sizeof(struct l2tp_avp_t) + sizeof(uint16_t)) { /* Hidden AVPs must contain at least two bytes for storing original attribute length */ log_warn("l2tp: incorrect hidden avp received (type %hu):" @@ -179,20 +164,22 @@ static int decode_avp(struct l2tp_avp_t *avp, const struct l2tp_attr_t *RV, MD5_Final(p1, &md5_ctx); if (attr_len <= MD5_DIGEST_LENGTH) { + /* The whole attribute fits in the first block: it is fully + deciphered, nothing more to do but to check its length */ memxor(avp->val, p1, attr_len); - return 0; + goto out; } memxor(p1, avp->val, MD5_DIGEST_LENGTH); - orig_attr_len = ntohs(*(uint16_t *)p1); + orig_attr_len = u_read_be16(p1); - if (orig_attr_len <= MD5_DIGEST_LENGTH - 2) { + if (orig_attr_len <= MD5_DIGEST_LENGTH - sizeof(uint16_t)) { /* Enough bytes decoded already, no need to decode padding */ memcpy(avp->val, p1, MD5_DIGEST_LENGTH); - return 0; + goto out; } - if (orig_attr_len > attr_len - 2) { + if (orig_attr_len > attr_len - sizeof(uint16_t)) { log_warn("l2tp: incorrect hidden avp received (type %hu):" " original attribute length too big (ciphered" " attribute length: %hu bytes, advertised original" @@ -203,7 +190,7 @@ static int decode_avp(struct l2tp_avp_t *avp, const struct l2tp_attr_t *RV, /* Decode remaining blocks. Start from the last block as preceding blocks must be kept hidden for computing MD5s */ - bytes_left = orig_attr_len + 2 - MD5_DIGEST_LENGTH; + bytes_left = orig_attr_len + sizeof(uint16_t) - MD5_DIGEST_LENGTH; last_block_len = bytes_left % MD5_DIGEST_LENGTH; blocks_left = bytes_left / MD5_DIGEST_LENGTH; if (last_block_len) { @@ -227,6 +214,23 @@ static int decode_avp(struct l2tp_avp_t *avp, const struct l2tp_attr_t *RV, } memcpy(avp->val, p1, MD5_DIGEST_LENGTH); +out: + /* The length prefix comes out of the cipher, so it is only as + trustworthy as the peer's knowledge of the shared secret. Bound it + against the room actually available in the received AVP before + letting it drive any read of the attribute value */ + orig_attr_len = u_read_be16(avp->val); + if (orig_attr_len > attr_len - sizeof(uint16_t)) { + log_warn("l2tp: incorrect hidden avp received (type %hu):" + " deciphered attribute length too big (ciphered" + " attribute length: %hu bytes, deciphered original" + " attribute length: %hu bytes), wrong secret?\n", + ntohs(avp->type), attr_len, orig_attr_len); + return -1; + } + + *orig_attr_len_out = orig_attr_len; + return 0; } @@ -240,6 +244,7 @@ int l2tp_recv(int fd, struct l2tp_packet_t **p, struct in_pktinfo *pkt_info, struct sockaddr_in addr; socklen_t addr_len; uint16_t orig_avp_len; + uint16_t orig_attr_len; void *orig_avp_val; uint8_t *buf, *ptr; int n, length; @@ -280,7 +285,7 @@ int l2tp_recv(int fd, struct l2tp_packet_t **p, struct in_pktinfo *pkt_info, ptr = (uint8_t *)(hdr + 1); addr_len = sizeof(addr); - n = recvfrom(fd, buf, L2TP_MAX_PACKET_SIZE, 0, &addr, &addr_len); + n = recvfrom(fd, buf, L2TP_MAX_PACKET_SIZE, 0, (struct sockaddr*)&addr, &addr_len); if (n < 0) { mempool_free(buf); if (errno == EAGAIN) { @@ -419,10 +424,11 @@ int l2tp_recv(int fd, struct l2tp_packet_t **p, struct in_pktinfo *pkt_info, ntohs(avp->type)); goto out_err; } - if (decode_avp(avp, RV, secret, secret_len) < 0) + if (decode_avp(avp, RV, secret, secret_len, + &orig_attr_len) < 0) goto out_err; - orig_avp_len = ntohs(*(uint16_t *)avp->val) + sizeof(*avp); + orig_avp_len = orig_attr_len + sizeof(*avp); orig_avp_val = avp->val + sizeof(uint16_t); } else { orig_avp_len = avp_len; @@ -444,17 +450,17 @@ int l2tp_recv(int fd, struct l2tp_packet_t **p, struct in_pktinfo *pkt_info, case ATTR_TYPE_INT16: if (orig_avp_len != sizeof(*avp) + 2) goto out_err_len; - attr->val.uint16 = ntohs(*(uint16_t *)orig_avp_val); + attr->val.uint16 = u_read_be16(orig_avp_val); break; case ATTR_TYPE_INT32: if (orig_avp_len != sizeof(*avp) + 4) goto out_err_len; - attr->val.uint32 = ntohl(*(uint32_t *)orig_avp_val); + attr->val.uint32 = u_read_be32(orig_avp_val); break; case ATTR_TYPE_INT64: if (orig_avp_len != sizeof(*avp) + 8) goto out_err_len; - attr->val.uint64 = be64toh(*(uint64_t *)orig_avp_val); + attr->val.uint64 = u_read_be64(orig_avp_val); break; case ATTR_TYPE_OCTETS: attr->val.octets = _malloc(attr->length); @@ -531,13 +537,13 @@ int l2tp_packet_send(int sock, struct l2tp_packet_t *pack) else switch (attr->attr->type) { case ATTR_TYPE_INT16: - *(int16_t *)avp->val = htons(attr->val.int16); + u_write_be16(avp->val, attr->val.int16); break; case ATTR_TYPE_INT32: - *(int32_t *)avp->val = htonl(attr->val.int32); + u_write_be32(avp->val, attr->val.int32); break; case ATTR_TYPE_INT64: - *(uint64_t *)avp->val = htobe64(attr->val.uint64); + u_write_be64(avp->val, attr->val.uint64); break; case ATTR_TYPE_STRING: case ATTR_TYPE_OCTETS: @@ -552,7 +558,7 @@ int l2tp_packet_send(int sock, struct l2tp_packet_t *pack) memcpy(buf, &pack->hdr, sizeof(pack->hdr)); hdr->flags = htons(pack->hdr.flags); - n = sendto(sock, buf, len, 0, &pack->addr, sizeof(pack->addr)); + n = sendto(sock, buf, len, 0, (struct sockaddr*)&pack->addr, sizeof(pack->addr)); mempool_free(buf); if (n < 0) { diff --git a/accel-pppd/ctrl/l2tp/packet_test.c b/accel-pppd/ctrl/l2tp/packet_test.c new file mode 100644 index 00000000..a6c9a182 --- /dev/null +++ b/accel-pppd/ctrl/l2tp/packet_test.c @@ -0,0 +1,491 @@ +/* + * Standalone regression test for the L2TP control message parser. + * + * Not part of the cmake build. Compile and run with: + * gcc -O1 -g -Wall -fno-strict-aliasing -D_GNU_SOURCE \ + * -fsanitize=address,undefined -fno-sanitize-recover=all \ + * -I accel-pppd -I accel-pppd/include -I accel-pppd/ctrl/l2tp \ + * -o /tmp/l2tp_packet_test \ + * accel-pppd/ctrl/l2tp/packet_test.c accel-pppd/ctrl/l2tp/packet.c \ + * -lcrypto && /tmp/l2tp_packet_test + * + * The interesting part is the hidden AVP subformat: the 2 bytes length prefix + * of a hidden AVP is an *output of the cipher*, so a peer using a different + * secret -- or an attacker injecting hidden AVPs blindly -- turns it into an + * essentially random 16 bits value. It must never be trusted to bound a read + * of the attribute value. + * + * The test drives the real parser through a real UDP socket: + * - hand-crafted packets exercise the hidden AVP length checks, including + * the single block (attribute <= 16 bytes) cipher path which the accel-ppp + * encoder itself never produces (it always pads by >= 16 bytes); + * - l2tp_packet_send()/l2tp_recv() round trips exercise the multi block + * cipher path and the unaligned AVP accessors. + * + * Everything packet.c needs besides libcrypto is stubbed below. + */ +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <unistd.h> +#include <arpa/inet.h> +#include <sys/socket.h> + +#include <openssl/md5.h> + +#include "triton.h" +#include "log.h" +#include "mempool.h" +#include "l2tp.h" +#include "attr_defs.h" + +static int failures; +#define CHECK(cond) do { if (!(cond)) { \ + fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); failures++; } } while (0) + +/* ------------------------------------------------------------------ stubs */ + +int conf_verbose = 1; +int conf_avp_permissive = 0; + +/* A dictionary just big enough for the attributes used here. Types are the + ones accel-ppp's own dictionary uses, except Tie_Breaker which is turned + into an INT64 to get coverage of the 64 bits accessor. */ +static struct l2tp_dict_attr_t dict[] = { + { .name = "Message-Type", .id = Message_Type, .type = ATTR_TYPE_INT16, .M = 1, .H = 0 }, + { .name = "Tie-Breaker", .id = Tie_Breaker, .type = ATTR_TYPE_INT64, .M = 0, .H = -1 }, + { .name = "Host-Name", .id = Host_Name, .type = ATTR_TYPE_STRING, .M = 1, .H = -1 }, + { .name = "Assigned-Tunnel-Id", .id = Assigned_Tunnel_ID, .type = ATTR_TYPE_INT16, .M = 1, .H = -1 }, + { .name = "Call-Serial-Number", .id = Call_Serial_Number, .type = ATTR_TYPE_INT32, .M = 1, .H = -1 }, + { .name = "Random-Vector", .id = Random_Vector, .type = ATTR_TYPE_OCTETS, .M = 1, .H = 0 }, +}; + +struct l2tp_dict_attr_t *l2tp_dict_find_attr_by_id(int id) +{ + size_t indx; + + for (indx = 0; indx < sizeof(dict) / sizeof(dict[0]); ++indx) + if (dict[indx].id == id) + return &dict[indx]; + + return NULL; +} + +const struct l2tp_dict_value_t *l2tp_dict_find_value(const struct l2tp_dict_attr_t *attr, + l2tp_value_t val) +{ + return NULL; +} + +/* Size carrying mempool: allocations stay exactly as large as the pool's + object size, so that ASan traps any read past the end of a packet buffer */ +mempool_t *mempool_create(int size) +{ + int *pool = malloc(sizeof(int)); + + *pool = size; + + return (mempool_t *)pool; +} + +void *mempool_alloc(mempool_t *pool) +{ + return malloc(*(int *)pool); +} + +void mempool_free(void *ptr) +{ + free(ptr); +} + +void triton_register_init(int order, void (*func)(void)) +{ + func(); +} + +int u_randbuf(void *buf, size_t buf_len, int *err) +{ + uint8_t *u8_buf = buf; + size_t indx; + + /* Deterministic on purpose: reproducible failures beat real entropy */ + for (indx = 0; indx < buf_len; ++indx) + u8_buf[indx] = (uint8_t)(indx * 7 + 0x5a); + + return 0; +} + +#define DEFINE_LOG_STUB(name) \ + void name(const char *fmt, ...) {} +DEFINE_LOG_STUB(log_emerg) +DEFINE_LOG_STUB(log_error) +DEFINE_LOG_STUB(log_warn) +DEFINE_LOG_STUB(log_ppp_debug) + +/* -------------------------------------------------------- packet building */ + +struct pktbuf { + uint8_t data[2048]; + size_t len; +}; + +static void pkt_init(struct pktbuf *pkt) +{ + struct l2tp_hdr_t hdr; + + memset(&hdr, 0, sizeof(hdr)); + hdr.flags = htons(L2TP_FLAG_T | L2TP_FLAG_L | L2TP_FLAG_S | 2); + + memset(pkt, 0, sizeof(*pkt)); + memcpy(pkt->data, &hdr, sizeof(hdr)); + pkt->len = sizeof(hdr); +} + +/* Append an AVP and return a pointer to its value */ +static uint8_t *pkt_add_avp(struct pktbuf *pkt, uint16_t extra_flags, + uint16_t type, const void *val, size_t val_len) +{ + struct l2tp_avp_t avp; + uint8_t *ptr = pkt->data + pkt->len; + + memset(&avp, 0, sizeof(avp)); + avp.flags = htons(extra_flags | ((sizeof(avp) + val_len) & L2TP_AVP_LEN_MASK)); + avp.type = htons(type); + + memcpy(ptr, &avp, sizeof(avp)); + if (val_len) + memcpy(ptr + sizeof(avp), val, val_len); + pkt->len += sizeof(avp) + val_len; + + return ptr + sizeof(avp); +} + +static void pkt_finish(struct pktbuf *pkt) +{ + uint16_t length = htons(pkt->len); + + memcpy(pkt->data + offsetof(struct l2tp_hdr_t, length), + &length, sizeof(length)); +} + +/* + * Cipher a hidden AVP whose cleartext (length prefix included) is at most one + * MD5 block long, i.e. the path that never validated the length prefix. + */ +static void hide_single_block(uint8_t *val, size_t val_len, uint16_t type, + const char *secret, size_t secret_len, + const uint8_t *rv, size_t rv_len) +{ + uint8_t md5[MD5_DIGEST_LENGTH]; + uint16_t attr_type = htons(type); + MD5_CTX md5_ctx; + size_t indx; + + MD5_Init(&md5_ctx); + MD5_Update(&md5_ctx, &attr_type, sizeof(attr_type)); + MD5_Update(&md5_ctx, secret, secret_len); + MD5_Update(&md5_ctx, rv, rv_len); + MD5_Final(md5, &md5_ctx); + + for (indx = 0; indx < val_len && indx < MD5_DIGEST_LENGTH; ++indx) + val[indx] ^= md5[indx]; +} + +/* --------------------------------------------------------------- plumbing */ + +static const char secret[] = "s3cr3t"; +static int sock = -1; +static struct sockaddr_in sock_addr; + +static void loopback_socket(void) +{ + socklen_t addr_len = sizeof(sock_addr); + + sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock < 0) { + perror("socket"); + exit(1); + } + + memset(&sock_addr, 0, sizeof(sock_addr)); + sock_addr.sin_family = AF_INET; + sock_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + if (bind(sock, (struct sockaddr *)&sock_addr, sizeof(sock_addr)) < 0 + || getsockname(sock, (struct sockaddr *)&sock_addr, &addr_len) < 0) { + perror("bind"); + exit(1); + } +} + +/* Feed raw bytes to the parser, NULL means "packet rejected" */ +static struct l2tp_packet_t *parse(const struct pktbuf *pkt) +{ + struct l2tp_packet_t *pack = NULL; + + if (sendto(sock, pkt->data, pkt->len, 0, + (struct sockaddr *)&sock_addr, sizeof(sock_addr)) < 0) { + perror("sendto"); + exit(1); + } + + CHECK(l2tp_recv(sock, &pack, NULL, secret, sizeof(secret) - 1) == 0); + + return pack; +} + +static const struct l2tp_attr_t *find_attr(const struct l2tp_packet_t *pack, int id) +{ + const struct l2tp_attr_t *attr; + + list_for_each_entry(attr, &pack->attrs, entry) + if (attr->attr->id == id) + return attr; + + return NULL; +} + +/* ------------------------------------------------------------------ tests */ + +/* + * A hidden AVP small enough to be ciphered in a single block: its deciphered + * length prefix used to be taken at face value, so anything up to 65535 was + * handed to the memcpy() feeding attr->val, reading way past the packet + * buffer. The parser must accept a prefix only if the attribute value it + * announces really fits in the received AVP. + */ +static void test_hidden_avp_length_prefix(void) +{ + static const struct { + const char *name; + size_t attr_len; /* ciphered attribute length */ + uint16_t declared; /* deciphered length prefix */ + int accept; + } cases[] = { + { "lies about 64K", 16, 0xffff, 0 }, + { "lies, minimal avp", 2, 0xffff, 0 }, + { "off by one", 16, 15, 0 }, + { "one byte too big", 4, 3, 0 }, + { "fits exactly", 16, 14, 1 }, + { "fits", 16, 4, 1 }, + { "empty value", 2, 0, 1 }, + { "no length prefix", 1, 0, 0 }, + }; + static const uint8_t rv[16] = { + 0xf3, 0x1a, 0x00, 0xff, 0x42, 0x7c, 0x91, 0x08, + 0x5d, 0xe6, 0x33, 0xb0, 0x14, 0xaa, 0x69, 0xc2, + }; + uint8_t value[MD5_DIGEST_LENGTH]; + struct l2tp_packet_t *pack; + const struct l2tp_attr_t *attr; + struct pktbuf pkt; + uint16_t declared; + size_t indx, i; + + for (indx = 0; indx < sizeof(cases) / sizeof(cases[0]); ++indx) { + pkt_init(&pkt); + pkt_add_avp(&pkt, L2TP_AVP_FLAG_M, Random_Vector, rv, sizeof(rv)); + + /* Cleartext: 2 bytes length prefix, then the value, then padding. + The value is a recognizable pattern so that a short read shows + up as wrong content rather than as a lucky pass. */ + memset(value, 0, sizeof(value)); + declared = htons(cases[indx].declared); + memcpy(value, &declared, cases[indx].attr_len < sizeof(declared) + ? cases[indx].attr_len : sizeof(declared)); + for (i = sizeof(declared); i < cases[indx].attr_len; ++i) + value[i] = 'a' + (i % 26); + + hide_single_block(value, cases[indx].attr_len, Host_Name, + secret, sizeof(secret) - 1, rv, sizeof(rv)); + pkt_add_avp(&pkt, L2TP_AVP_FLAG_M | L2TP_AVP_FLAG_H, Host_Name, + value, cases[indx].attr_len); + pkt_finish(&pkt); + + pack = parse(&pkt); + if (!cases[indx].accept) { + if (pack) { + fprintf(stderr, "FAIL %s:%d: hidden avp accepted" + " (%s)\n", __FILE__, __LINE__, + cases[indx].name); + failures++; + l2tp_packet_free(pack); + } + continue; + } + + if (!pack) { + fprintf(stderr, "FAIL %s:%d: hidden avp rejected (%s)\n", + __FILE__, __LINE__, cases[indx].name); + failures++; + continue; + } + + attr = find_attr(pack, Host_Name); + CHECK(attr != NULL); + if (attr) { + CHECK(attr->length == cases[indx].declared); + for (i = 0; i < cases[indx].declared; ++i) + CHECK((uint8_t)attr->val.string[i] == + 'a' + ((i + sizeof(declared)) % 26)); + CHECK(attr->val.string[cases[indx].declared] == '\0'); + } + l2tp_packet_free(pack); + } +} + +/* + * A hidden AVP is rejected outright when no Random Vector was received, or + * when its length cannot even hold the length prefix. + */ +static void test_hidden_avp_prerequisites(void) +{ + static const uint8_t value[16] = { 0 }; + struct l2tp_packet_t *pack; + struct pktbuf pkt; + + pkt_init(&pkt); + pkt_add_avp(&pkt, L2TP_AVP_FLAG_M | L2TP_AVP_FLAG_H, Host_Name, + value, sizeof(value)); + pkt_finish(&pkt); + pack = parse(&pkt); + CHECK(pack == NULL); + if (pack) + l2tp_packet_free(pack); + + /* Random Vector present, but the hidden AVP carries no value at all */ + pkt_init(&pkt); + pkt_add_avp(&pkt, L2TP_AVP_FLAG_M, Random_Vector, value, sizeof(value)); + pkt_add_avp(&pkt, L2TP_AVP_FLAG_M | L2TP_AVP_FLAG_H, Host_Name, NULL, 0); + pkt_finish(&pkt); + pack = parse(&pkt); + CHECK(pack == NULL); + if (pack) + l2tp_packet_free(pack); +} + +/* + * Round trip through the real encoder. With hide_avps set every attribute but + * Message-Type and Random-Vector goes through the multi block cipher, since + * encode_attr() always appends at least 16 bytes of padding. + */ +static void test_roundtrip(int hide_avps) +{ + static const char host_name[] = "accel-ppp regression test host name"; + struct l2tp_packet_t *pack; + const struct l2tp_attr_t *attr; + int ret; + + pack = l2tp_packet_alloc(2, Message_Type_Hello, &sock_addr, hide_avps, + secret, sizeof(secret) - 1); + CHECK(pack != NULL); + if (!pack) + return; + + /* Odd length string first: everything after it sits on an odd offset, + so the integer accessors below run unaligned */ + CHECK(l2tp_packet_add_string(pack, Host_Name, host_name, 1) == 0); + CHECK(l2tp_packet_add_int16(pack, Assigned_Tunnel_ID, 0x1234, 1) == 0); + CHECK(l2tp_packet_add_int32(pack, Call_Serial_Number, 0x89abcdef, 1) == 0); + CHECK(l2tp_packet_add_int64(pack, Tie_Breaker, 0x0123456789abcdefULL, 0) == 0); + + ret = l2tp_packet_send(sock, pack); + CHECK(ret == 0); + l2tp_packet_free(pack); + if (ret < 0) + return; + + pack = NULL; + CHECK(l2tp_recv(sock, &pack, NULL, secret, sizeof(secret) - 1) == 0); + CHECK(pack != NULL); + if (!pack) + return; + + attr = find_attr(pack, Message_Type); + CHECK(attr && attr->val.uint16 == Message_Type_Hello); + attr = find_attr(pack, Host_Name); + CHECK(attr && attr->length == (int)strlen(host_name)); + CHECK(attr && strcmp(attr->val.string, host_name) == 0); + attr = find_attr(pack, Assigned_Tunnel_ID); + CHECK(attr && attr->val.uint16 == 0x1234); + attr = find_attr(pack, Call_Serial_Number); + CHECK(attr && attr->val.uint32 == 0x89abcdef); + attr = find_attr(pack, Tie_Breaker); + CHECK(attr && attr->val.uint64 == 0x0123456789abcdefULL); + + l2tp_packet_free(pack); +} + +/* + * A hidden AVP deciphered with the wrong secret yields a random length + * prefix. Whatever it is, the parser must not read outside the AVP. + */ +static void test_wrong_secret(void) +{ + struct l2tp_packet_t *pack; + struct pktbuf pkt; + uint8_t buf[1024]; + size_t len, indx; + int ret; + + pack = l2tp_packet_alloc(2, Message_Type_Hello, &sock_addr, 1, + secret, sizeof(secret) - 1); + CHECK(pack != NULL); + if (!pack) + return; + + CHECK(l2tp_packet_add_string(pack, Host_Name, "hidden", 1) == 0); + CHECK(l2tp_packet_send(sock, pack) == 0); + l2tp_packet_free(pack); + + len = recv(sock, buf, sizeof(buf), 0); + CHECK(len > 0); + + /* Same bytes on the wire, every other secret at the receiving end */ + for (indx = 0; indx < 64; ++indx) { + char wrong[8]; + + snprintf(wrong, sizeof(wrong), "wrong%02zu", indx); + memcpy(pkt.data, buf, len); + pkt.len = len; + + if (sendto(sock, pkt.data, pkt.len, 0, + (struct sockaddr *)&sock_addr, sizeof(sock_addr)) < 0) { + perror("sendto"); + exit(1); + } + pack = NULL; + ret = l2tp_recv(sock, &pack, NULL, wrong, strlen(wrong)); + CHECK(ret == 0); + if (pack) { + /* Accepting is fine (the random prefix may happen to be + plausible), reading out of the AVP is not */ + const struct l2tp_attr_t *attr = find_attr(pack, Host_Name); + + CHECK(!attr || attr->length <= (int)len); + l2tp_packet_free(pack); + } + } +} + +int main(void) +{ + loopback_socket(); + + test_hidden_avp_length_prefix(); + test_hidden_avp_prerequisites(); + test_roundtrip(0); + test_roundtrip(1); + test_wrong_secret(); + + close(sock); + + if (failures) { + fprintf(stderr, "%d failure(s)\n", failures); + return 1; + } + + printf("all tests passed\n"); + + return 0; +} diff --git a/accel-pppd/ctrl/pppoe/CMakeLists.txt b/accel-pppd/ctrl/pppoe/CMakeLists.txt index fd4f9a36..92733b74 100644 --- a/accel-pppd/ctrl/pppoe/CMakeLists.txt +++ b/accel-pppd/ctrl/pppoe/CMakeLists.txt @@ -13,7 +13,12 @@ SET(sources ${sources} tr101.c) ENDIF(RADIUS) ADD_LIBRARY(pppoe SHARED ${sources}) -TARGET_LINK_LIBRARIES(pppoe vlan-mon connlimit) +# if MUSL is set then we need to link with the connlimit library +IF (MUSL) + TARGET_LINK_LIBRARIES(pppoe vlan-mon connlimit ${crypto_lib}) +ELSE (MUSL) + TARGET_LINK_LIBRARIES(pppoe vlan-mon ${crypto_lib}) +ENDIF (MUSL) set_property(TARGET pppoe PROPERTY CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) set_property(TARGET pppoe PROPERTY INSTALL_RPATH ${CMAKE_INSTALL_PREFIX}/lib${LIB_SUFFIX}/accel-ppp) diff --git a/accel-pppd/ctrl/pppoe/cli.c b/accel-pppd/ctrl/pppoe/cli.c index d8399543..453c0cf3 100644 --- a/accel-pppd/ctrl/pppoe/cli.c +++ b/accel-pppd/ctrl/pppoe/cli.c @@ -88,16 +88,20 @@ help: static int show_stat_exec(const char *cmd, char * const *fields, int fields_cnt, void *client) { + struct pppoe_stat_t stat; + + pppoe_stat_get(&stat); + cli_send(client, "pppoe:\r\n"); - cli_sendv(client, " starting: %u\r\n", stat_starting); - cli_sendv(client, " active: %u\r\n", stat_active); - cli_sendv(client, " delayed PADO: %u\r\n", stat_delayed_pado); - cli_sendv(client, " recv PADI: %lu\r\n", stat_PADI_recv); - cli_sendv(client, " drop PADI: %lu\r\n", stat_PADI_drop); - cli_sendv(client, " sent PADO: %lu\r\n", stat_PADO_sent); - cli_sendv(client, " recv PADR(dup): %lu(%lu)\r\n", stat_PADR_recv, stat_PADR_dup_recv); - cli_sendv(client, " sent PADS: %lu\r\n", stat_PADS_sent); - cli_sendv(client, " filtered: %lu\r\n", stat_filtered); + cli_sendv(client, " starting: %u\r\n", stat.starting); + cli_sendv(client, " active: %u\r\n", stat.active); + cli_sendv(client, " delayed PADO: %u\r\n", stat.delayed_PADO); + cli_sendv(client, " recv PADI: %lu\r\n", stat.PADI_recv); + cli_sendv(client, " drop PADI: %lu\r\n", stat.PADI_drop); + cli_sendv(client, " sent PADO: %lu\r\n", stat.PADO_sent); + cli_sendv(client, " recv PADR(dup): %lu(%lu)\r\n", stat.PADR_recv, stat.PADR_dup_recv); + cli_sendv(client, " sent PADS: %lu\r\n", stat.PADS_sent); + cli_sendv(client, " filtered: %lu\r\n", stat.filtered); return CLI_CMD_OK; } diff --git a/accel-pppd/ctrl/pppoe/disc.c b/accel-pppd/ctrl/pppoe/disc.c index 8a82e1d2..f7f5c781 100644 --- a/accel-pppd/ctrl/pppoe/disc.c +++ b/accel-pppd/ctrl/pppoe/disc.c @@ -77,7 +77,7 @@ static struct disc_net *init_net(const struct ap_net *net) fcntl(sock, F_SETFD, FD_CLOEXEC); net->set_nonblocking(sock, 1); - n = _malloc(sizeof(*net) + (HASH_BITS + 1) * sizeof(struct tree)); + n = _malloc(sizeof(*n) + (HASH_BITS + 1) * sizeof(struct tree)); tree = n->tree; for (i = 0; i <= HASH_BITS; i++) { @@ -110,7 +110,7 @@ static void free_net(struct disc_net *net) pthread_mutex_lock(&nets_lock); for (i = 0; i < MAX_NET; i++) { if (nets[i] == net) { - memcpy(nets + i, nets + i + 1, net_cnt - i - 1); + memmove(nets + i, nets + i + 1, (net_cnt - i - 1) * sizeof(nets[0])); net_cnt--; break; } @@ -329,7 +329,7 @@ static int disc_read(struct triton_md_handler_t *h) } if (mac_filter_check(ethhdr->h_source)) { - __sync_add_and_fetch(&stat_filtered, 1); + pppoe_stat_add_filtered(); continue; } @@ -363,6 +363,7 @@ static int disc_read(struct triton_md_handler_t *h) if (hdr->type != 1) { if (conf_verbose) log_warn("pppoe: discarding packet (unsupported type %i)\n", hdr->type); + continue; } if (forward(net, src.sll_ifindex, pack, n)) diff --git a/accel-pppd/ctrl/pppoe/dpado.c b/accel-pppd/ctrl/pppoe/dpado.c index 71faa130..dc570dda 100644 --- a/accel-pppd/ctrl/pppoe/dpado.c +++ b/accel-pppd/ctrl/pppoe/dpado.c @@ -11,6 +11,7 @@ #include "triton.h" #include "log.h" #include "memdebug.h" +#include "utils.h" #include "pppoe.h" @@ -60,27 +61,15 @@ void dpado_check_prev(int conn_cnt) pthread_mutex_unlock(&dpado_range_lock); } -static void strip(char *str) -{ - char *ptr = str; - char *endptr = strchr(str, 0); - while (1) { - ptr = strchr(ptr, ' '); - if (ptr) - memmove(ptr, ptr + 1, endptr - ptr - 1); - else - break; - } -} - int dpado_parse(const char *str) { char *str1 = _strdup(str); char *ptr1, *ptr2, *ptr3, *endptr; + unsigned int active = pppoe_stat_active(); LIST_HEAD(range_list); struct dpado_range_t *r; - strip(str1); + u_strstrip(str1, ' '); ptr1 = str1; @@ -96,17 +85,23 @@ int dpado_parse(const char *str) memset(r, 0, sizeof(*r)); r->pado_delay = strtol(ptr1, &endptr, 10); - if (*endptr) + if (*endptr) { + _free(r); goto out_err; + } if (list_empty(&range_list)) r->conn_cnt = INT_MAX; else { - if (!ptr3) + if (!ptr3) { + _free(r); goto out_err; + } r->conn_cnt = strtol(ptr3 + 1, &endptr, 10); - if (*endptr) + if (*endptr) { + _free(r); goto out_err; + } } list_add_tail(&r->entry, &range_list); @@ -131,7 +126,7 @@ int dpado_parse(const char *str) dpado_range_prev = NULL; list_for_each_entry(r, &dpado_range_list, entry) { - if (!dpado_range_prev || stat_active >= r->conn_cnt) { + if (!dpado_range_prev || active >= r->conn_cnt) { dpado_range_prev = r; if (r->entry.next != &dpado_range_list) dpado_range_next = list_entry(r->entry.next, typeof(*r), entry); @@ -159,6 +154,11 @@ int dpado_parse(const char *str) return 0; out_err: + while (!list_empty(&range_list)) { + r = list_entry(range_list.next, typeof(*r), entry); + list_del(&r->entry); + _free(r); + } _free(str1); log_emerg("pppoe: pado_delay: invalid format\n"); return -1; diff --git a/accel-pppd/ctrl/pppoe/mac_filter.c b/accel-pppd/ctrl/pppoe/mac_filter.c index ba78df6b..3a6a00d6 100644 --- a/accel-pppd/ctrl/pppoe/mac_filter.c +++ b/accel-pppd/ctrl/pppoe/mac_filter.c @@ -92,15 +92,17 @@ static int mac_filter_load(const char *opt) log_warn("pppoe: mac-filter:%s:%i: address is invalid\n", name, line); continue; } - mac = _malloc(sizeof(*mac)); for (i = 0; i < ETH_ALEN; i++) { - if (n[i] > 255) { - log_warn("pppoe: mac-filter:%s:%i: address is invalid\n", name, line); - _free(mac); - continue; - } - mac->addr[i] = n[i]; + if (n[i] > 255) + break; } + if (i < ETH_ALEN) { + log_warn("pppoe: mac-filter:%s:%i: address is invalid\n", name, line); + continue; + } + mac = _malloc(sizeof(*mac)); + for (i = 0; i < ETH_ALEN; i++) + mac->addr[i] = n[i]; list_add_tail(&mac->entry, &mac_list); } pthread_rwlock_unlock(&lock); diff --git a/accel-pppd/ctrl/pppoe/pppoe.c b/accel-pppd/ctrl/pppoe/pppoe.c index dd623acc..bd92cbf8 100644 --- a/accel-pppd/ctrl/pppoe/pppoe.c +++ b/accel-pppd/ctrl/pppoe/pppoe.c @@ -11,11 +11,6 @@ #include <net/ethernet.h> #include <netpacket/packet.h> #include <arpa/inet.h> -#ifdef HAVE_PRINTF_H -#include <printf.h> -#endif - -#include "crypto.h" #include "events.h" #include "triton.h" @@ -29,6 +24,7 @@ #endif #include "iputils.h" +#include "utils.h" #include "connlimit.h" #include "vlan_mon.h" @@ -38,16 +34,13 @@ #define SID_MAX 65536 -#ifndef min -#define min(x,y) ((x)<(y)?(x):(y)) -#endif - struct pppoe_conn_t { struct list_head entry; struct triton_context_t ctx; struct pppoe_serv_t *serv; uint16_t sid; uint8_t addr[ETH_ALEN]; + unsigned int ppp_starting:1; unsigned int ppp_started:1; struct pppoe_tag *relay_sid; @@ -83,7 +76,7 @@ struct padi_t }; struct iplink_arg { - pcre *re; + pcre2_code *re; const char *opt; void *cli; long *arg1; @@ -114,17 +107,8 @@ static mempool_t conn_pool; static mempool_t pado_pool; static mempool_t padi_pool; -unsigned int stat_starting; -unsigned int stat_active; -unsigned int stat_delayed_pado; -unsigned long stat_PADI_recv; -unsigned long stat_PADI_drop; -unsigned long stat_PADO_sent; -unsigned long stat_PADR_recv; -unsigned long stat_PADR_dup_recv; -unsigned long stat_PADS_sent; +static struct pppoe_stat_t pppoe_stat; unsigned int total_padi_cnt; -unsigned long stat_filtered; pthread_rwlock_t serv_lock = PTHREAD_RWLOCK_INITIALIZER; LIST_HEAD(serv_list); @@ -136,6 +120,35 @@ static unsigned long *sid_map; static unsigned long *sid_ptr; static int sid_idx; +void __export pppoe_stat_get(struct pppoe_stat_t *stat) +{ + stat->starting = __atomic_load_n(&pppoe_stat.starting, __ATOMIC_RELAXED); + stat->active = __atomic_load_n(&pppoe_stat.active, __ATOMIC_RELAXED); + stat->delayed_PADO = __atomic_load_n(&pppoe_stat.delayed_PADO, __ATOMIC_RELAXED); + stat->PADI_recv = __atomic_load_n(&pppoe_stat.PADI_recv, __ATOMIC_RELAXED); + stat->PADI_drop = __atomic_load_n(&pppoe_stat.PADI_drop, __ATOMIC_RELAXED); + stat->PADO_sent = __atomic_load_n(&pppoe_stat.PADO_sent, __ATOMIC_RELAXED); + stat->PADR_recv = __atomic_load_n(&pppoe_stat.PADR_recv, __ATOMIC_RELAXED); + stat->PADR_dup_recv = __atomic_load_n(&pppoe_stat.PADR_dup_recv, __ATOMIC_RELAXED); + stat->PADS_sent = __atomic_load_n(&pppoe_stat.PADS_sent, __ATOMIC_RELAXED); + stat->filtered = __atomic_load_n(&pppoe_stat.filtered, __ATOMIC_RELAXED); +} + +unsigned int __export pppoe_stat_starting(void) +{ + return __atomic_load_n(&pppoe_stat.starting, __ATOMIC_RELAXED); +} + +unsigned int __export pppoe_stat_active(void) +{ + return __atomic_load_n(&pppoe_stat.active, __ATOMIC_RELAXED); +} + +void __export pppoe_stat_add_filtered(void) +{ + __atomic_add_fetch(&pppoe_stat.filtered, 1, __ATOMIC_RELAXED); +} + static uint8_t bc_addr[ETH_ALEN] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; static void pppoe_send_PADT(struct pppoe_conn_t *conn); @@ -173,9 +186,12 @@ static void disconnect(struct pppoe_conn_t *conn) struct pppoe_serv_t *serv = conn->serv; if (conn->ppp_started) { - dpado_check_prev(__sync_fetch_and_sub(&stat_active, 1)); + dpado_check_prev(__atomic_fetch_sub(&pppoe_stat.active, 1, __ATOMIC_RELAXED)); conn->ppp_started = 0; ap_session_terminate(&conn->ppp.ses, TERM_USER_REQUEST, 1); + } else if (conn->ppp_starting) { + __atomic_sub_fetch(&pppoe_stat.starting, 1, __ATOMIC_RELAXED); + conn->ppp_starting = 0; } pppoe_send_PADT(conn); @@ -232,7 +248,7 @@ static void ppp_finished(struct ap_session *ses) log_ppp_debug("pppoe: ppp finished\n"); if (conn->ppp_started) { - dpado_check_prev(__sync_fetch_and_sub(&stat_active, 1)); + dpado_check_prev(__atomic_fetch_sub(&pppoe_stat.active, 1, __ATOMIC_RELAXED)); conn->ppp_started = 0; triton_context_call(&conn->ctx, (triton_event_func)disconnect, conn); } @@ -279,9 +295,15 @@ static void pppoe_conn_ctx_switch(struct triton_context_t *ctx, void *arg) static struct pppoe_conn_t *allocate_channel(struct pppoe_serv_t *serv, const uint8_t *addr, const struct pppoe_tag *host_uniq, const struct pppoe_tag *relay_sid, const struct pppoe_tag *service_name, const struct pppoe_tag *tr101, const uint8_t *cookie, uint16_t ppp_max_payload) { + struct pppoe_tag empty_service_name = { + .tag_type = htons(TAG_SERVICE_NAME), + }; struct pppoe_conn_t *conn; unsigned long *old_sid_ptr; + if (!service_name) + service_name = &empty_service_name; + conn = mempool_alloc(conn_pool); if (!conn) { log_error("pppoe: out of memory\n"); @@ -450,6 +472,9 @@ static void connect_channel(struct pppoe_conn_t *conn) struct sockaddr_pppox sp; triton_event_fire(EV_CTRL_STARTING, &conn->ppp.ses); + conn->ppp_starting = 1; + __atomic_add_fetch(&pppoe_stat.starting, 1, __ATOMIC_RELAXED); + triton_event_fire(EV_CTRL_STARTED, &conn->ppp.ses); sock = net->socket(AF_PPPOX, SOCK_DGRAM, PX_PROTO_OE); @@ -486,9 +511,11 @@ static void connect_channel(struct pppoe_conn_t *conn) } #endif + conn->ppp_starting = 0; conn->ppp_started = 1; - dpado_check_next(__sync_add_and_fetch(&stat_active, 1)); + __atomic_sub_fetch(&pppoe_stat.starting, 1, __ATOMIC_RELAXED); + dpado_check_next(__atomic_add_fetch(&pppoe_stat.active, 1, __ATOMIC_RELAXED)); return; @@ -529,7 +556,12 @@ static void print_tag_octets(struct pppoe_tag *tag) static void print_tag_u16(struct pppoe_tag *tag) { - log_info2("%i", (uint16_t)ntohs(*(uint16_t *)tag->tag_data)); + if (ntohs(tag->tag_len) != sizeof(uint16_t)) { + log_info2("invalid"); + return; + } + + log_info2("%i", u_read_be16(tag->tag_data)); } static void print_packet(const char *ifname, const char *op, uint8_t *pack) @@ -606,7 +638,7 @@ static void print_packet(const char *ifname, const char *op, uint8_t *pack) if (ntohs(tag->tag_len) < 4) log_info2(" <Vendor-Specific invalid>"); else - log_info2(" <Vendor-Specific %x>", ntohl(*(uint32_t *)tag->tag_data)); + log_info2(" <Vendor-Specific %x>", u_read_be32(tag->tag_data)); break; case TAG_RELAY_SESSION_ID: log_info2(" <Relay-Session-Id "); @@ -674,7 +706,10 @@ static void generate_cookie(struct pppoe_serv_t *serv, const uint8_t *src, uint8 } else memset(u1.raw + 16, 0, 4); - *(uint32_t *)(u1.raw + 20) = ts.tv_sec + conf_cookie_timeout; + { + uint32_t expires = ts.tv_sec + conf_cookie_timeout; + memcpy(u1.raw + 20, &expires, sizeof(expires)); + } for (i = 0; i < 3; i++) DES_ecb_encrypt(&u1.b[i], &u2.b[i], &ks, DES_ENCRYPT); @@ -712,8 +747,12 @@ static int check_cookie(struct pppoe_serv_t *serv, const uint8_t *src, const uin for (i = 0; i < 3; i++) DES_ecb_encrypt(&u2.b[i], &u1.b[i], &ks, DES_DECRYPT); - if (*(uint32_t *)(u1.raw + 20) < ts.tv_sec) - return 1; + { + uint32_t expires; + memcpy(&expires, u1.raw + 20, sizeof(expires)); + if (expires < ts.tv_sec) + return 1; + } MD5_Init(&ctx); MD5_Update(&ctx, serv->secret, SECRET_LENGTH); @@ -762,7 +801,7 @@ static int add_tag2(uint8_t *pack, size_t pack_size, const struct pppoe_tag *t) { struct pppoe_hdr *hdr = (struct pppoe_hdr *)(pack + ETH_HLEN); struct pppoe_tag *tag = (struct pppoe_tag *)(pack + ETH_HLEN + sizeof(*hdr) + ntohs(hdr->length)); - if (pack_size <= ETH_HLEN + sizeof(*hdr) + ntohs(hdr->length) + ntohs(t->tag_len) || ntohs(t->tag_len) < 0) + if (pack_size <= ETH_HLEN + sizeof(*hdr) + ntohs(hdr->length) + sizeof(*t) + ntohs(t->tag_len)) return -1; memcpy(tag, t, sizeof(*t) + ntohs(t->tag_len)); @@ -822,7 +861,7 @@ static void pppoe_send_PADO(struct pppoe_serv_t *serv, const uint8_t *addr, cons if (conf_verbose) print_packet(serv->ifname, "send", pack); - __sync_add_and_fetch(&stat_PADO_sent, 1); + __atomic_add_fetch(&pppoe_stat.PADO_sent, 1, __ATOMIC_RELAXED); pppoe_send(serv, pack); } @@ -871,7 +910,7 @@ static void pppoe_send_PADS(struct pppoe_conn_t *conn) if (conf_verbose) print_packet(conn->serv->ifname, "send", pack); - __sync_add_and_fetch(&stat_PADS_sent, 1); + __atomic_add_fetch(&pppoe_stat.PADS_sent, 1, __ATOMIC_RELAXED); pppoe_send(conn->serv, pack); } @@ -898,7 +937,7 @@ static void free_delayed_pado(struct delayed_pado_t *pado) { triton_timer_del(&pado->timer); - __sync_sub_and_fetch(&stat_delayed_pado, 1); + __atomic_sub_fetch(&pppoe_stat.delayed_PADO, 1, __ATOMIC_RELAXED); list_del(&pado->entry); if (pado->host_uniq) @@ -984,19 +1023,19 @@ static void pppoe_recv_PADI(struct pppoe_serv_t *serv, uint8_t *pack, int size) struct timespec ts; uint16_t ppp_max_payload = 0; - __sync_add_and_fetch(&stat_PADI_recv, 1); + __atomic_add_fetch(&pppoe_stat.PADI_recv, 1, __ATOMIC_RELAXED); if (ap_shutdown || pado_delay == -1) return; - if (conf_max_starting && ap_session_stat.starting >= conf_max_starting) + if (conf_max_starting && ap_session_stat_starting() >= conf_max_starting) return; - if (conf_max_sessions && ap_session_stat.active + ap_session_stat.starting >= conf_max_sessions) + if (conf_max_sessions && ap_session_stat_active() + ap_session_stat_starting() >= conf_max_sessions) return; if (check_padi_limit(serv, ethhdr->h_source)) { - __sync_add_and_fetch(&stat_PADI_drop, 1); + __atomic_add_fetch(&pppoe_stat.PADI_drop, 1, __ATOMIC_RELAXED); if (conf_verbose) { clock_gettime(CLOCK_MONOTONIC, &ts); if (ts.tv_sec - 60 >= serv->last_padi_limit_warn) { @@ -1013,11 +1052,13 @@ static void pppoe_recv_PADI(struct pppoe_serv_t *serv, uint8_t *pack, int size) len = ntohs(hdr->length); for (n = 0; n < len; n += sizeof(*tag) + ntohs(tag->tag_len)) { tag = (struct pppoe_tag *)(pack + ETH_HLEN + sizeof(*hdr) + n); + if (n + sizeof(*tag) > len) + return; if (n + sizeof(*tag) + ntohs(tag->tag_len) > len) return; switch (ntohs(tag->tag_type)) { case TAG_END_OF_LIST: - break; + goto tags_done; case TAG_SERVICE_NAME: if (tag->tag_len == 0 && conf_accept_blank_service) { service_match = 1; @@ -1044,10 +1085,11 @@ static void pppoe_recv_PADI(struct pppoe_serv_t *serv, uint8_t *pack, int size) break; case TAG_PPP_MAX_PAYLOAD: if (ntohs(tag->tag_len) == 2) - ppp_max_payload = ntohs(*(uint16_t *)tag->tag_data); + ppp_max_payload = u_read_be16(tag->tag_data); break; } } +tags_done: if (conf_verbose) print_packet(serv->ifname, "recv", pack); @@ -1098,7 +1140,7 @@ static void pppoe_recv_PADI(struct pppoe_serv_t *serv, uint8_t *pack, int size) triton_timer_add(&serv->ctx, &pado->timer, 0); list_add_tail(&pado->entry, &serv->pado_list); - __sync_add_and_fetch(&stat_delayed_pado, 1); + __atomic_add_fetch(&pppoe_stat.delayed_PADO, 1, __ATOMIC_RELAXED); } else pppoe_send_PADO(serv, ethhdr->h_source, host_uniq_tag, relay_sid_tag, service_name_tag, ppp_max_payload); } @@ -1118,15 +1160,15 @@ static void pppoe_recv_PADR(struct pppoe_serv_t *serv, uint8_t *pack, int size) int vendor_id; uint16_t ppp_max_payload = 0; - __sync_add_and_fetch(&stat_PADR_recv, 1); + __atomic_add_fetch(&pppoe_stat.PADR_recv, 1, __ATOMIC_RELAXED); if (ap_shutdown) return; - if (conf_max_starting && ap_session_stat.starting >= conf_max_starting) + if (conf_max_starting && ap_session_stat_starting() >= conf_max_starting) return; - if (conf_max_sessions && ap_session_stat.active + ap_session_stat.starting >= conf_max_sessions) + if (conf_max_sessions && ap_session_stat_active() + ap_session_stat_starting() >= conf_max_sessions) return; if (!memcmp(ethhdr->h_dest, bc_addr, ETH_ALEN)) { @@ -1159,7 +1201,7 @@ static void pppoe_recv_PADR(struct pppoe_serv_t *serv, uint8_t *pack, int size) } switch (ntohs(tag->tag_type)) { case TAG_END_OF_LIST: - break; + goto padr_tags_done; case TAG_SERVICE_NAME: service_name_tag = tag; if (tag->tag_len == 0) @@ -1190,16 +1232,18 @@ static void pppoe_recv_PADR(struct pppoe_serv_t *serv, uint8_t *pack, int size) case TAG_VENDOR_SPECIFIC: if (ntohs(tag->tag_len) < 4) continue; - vendor_id = ntohl(*(uint32_t *)tag->tag_data); + vendor_id = u_read_be32(tag->tag_data); if (vendor_id == VENDOR_ADSL_FORUM) if (conf_tr101) tr101_tag = tag; + break; case TAG_PPP_MAX_PAYLOAD: if (ntohs(tag->tag_len) == 2) - ppp_max_payload = ntohs(*(uint16_t *)tag->tag_data); + ppp_max_payload = u_read_be16(tag->tag_data); break; } } +padr_tags_done: if (!ac_cookie_tag) { if (conf_verbose) @@ -1207,6 +1251,12 @@ static void pppoe_recv_PADR(struct pppoe_serv_t *serv, uint8_t *pack, int size) return; } + if (!service_name_tag) { + if (conf_verbose) + log_warn("pppoe: discard PADR packet (no Service-Name tag present)\n"); + return; + } + if (ntohs(ac_cookie_tag->tag_len) != COOKIE_LENGTH) { if (conf_verbose) log_warn("pppoe: discard PADR packet (incorrect AC-Cookie tag length)\n"); @@ -1229,7 +1279,7 @@ static void pppoe_recv_PADR(struct pppoe_serv_t *serv, uint8_t *pack, int size) pthread_mutex_lock(&serv->lock); conn = find_channel(serv, (uint8_t *)ac_cookie_tag->tag_data); if (conn && !conn->ppp.ses.username) { - __sync_add_and_fetch(&stat_PADR_dup_recv, 1); + __atomic_add_fetch(&pppoe_stat.PADR_dup_recv, 1, __ATOMIC_RELAXED); pppoe_send_PADS(conn); } pthread_mutex_unlock(&serv->lock); @@ -1368,8 +1418,12 @@ out_err: static int __pppoe_add_interface_re(int index, int flags, const char *name, int iflink, int vid, struct iplink_arg *arg) { - if (pcre_exec(arg->re, NULL, name, strlen(name), 0, 0, NULL, 0) < 0) + pcre2_match_data *match_data = pcre2_match_data_create(0, NULL); + if (pcre2_match(arg->re, (PCRE2_SPTR)name, strlen(name), 0, 0, match_data, NULL) < 0) { + pcre2_match_data_free(match_data); return 0; + } + pcre2_match_data_free(match_data); __pppoe_server_start(name, arg->opt, arg->cli, iflink, vid, 0); @@ -1378,11 +1432,11 @@ static int __pppoe_add_interface_re(int index, int flags, const char *name, int static void pppoe_add_interface_re(const char *opt, void *cli) { - pcre *re = NULL; - const char *pcre_err; + pcre2_code *re = NULL; + int pcre_err; char *pattern; const char *ptr; - int pcre_offset; + PCRE2_SIZE pcre_offset; struct iplink_arg arg; for (ptr = opt; *ptr && *ptr != ','; ptr++); @@ -1391,10 +1445,14 @@ static void pppoe_add_interface_re(const char *opt, void *cli) memcpy(pattern, opt + 3, ptr - (opt + 3)); pattern[ptr - (opt + 3)] = 0; - re = pcre_compile2(pattern, 0, NULL, &pcre_err, &pcre_offset, NULL); + re = pcre2_compile((PCRE2_SPTR)pattern, PCRE2_ZERO_TERMINATED, 0, &pcre_err, &pcre_offset, NULL); if (!re) { - log_error("pppoe: %s at %i\r\n", pcre_err, pcre_offset); + PCRE2_UCHAR err_msg[64]; + pcre2_get_error_message(pcre_err, err_msg, sizeof(err_msg)); + if (cli) + cli_sendv(cli, "pppoe: %s at %i\r\n", err_msg, (int)pcre_offset); + log_error("pppoe: %s at %i\r\n", err_msg, (int)pcre_offset); return; } @@ -1404,7 +1462,7 @@ static void pppoe_add_interface_re(const char *opt, void *cli) iplink_list((iplink_list_func)__pppoe_add_interface_re, &arg); - pcre_free(re); + pcre2_code_free(re); _free(pattern); } @@ -1637,12 +1695,6 @@ void pppoe_server_stop(const char *ifname) pthread_rwlock_unlock(&serv_lock); } -void __export pppoe_get_stat(unsigned int **starting, unsigned int **active) -{ - *starting = &stat_starting; - *active = &stat_active; -} - static int init_secret(struct pppoe_serv_t *serv) { DES_cblock key; @@ -1676,10 +1728,10 @@ void pppoe_vlan_mon_notify(int ifindex, int vid, int vlan_ifindex) struct ifreq ifr; char *ptr; int len, r, svid; - pcre *re = NULL; - const char *pcre_err; + pcre2_code *re = NULL; + int pcre_err; char *pattern; - int pcre_offset; + PCRE2_SIZE pcre_offset; char ifname[IFNAMSIZ]; if (!sect) @@ -1777,15 +1829,17 @@ void pppoe_vlan_mon_notify(int ifindex, int vid, int vlan_ifindex) memcpy(pattern, opt->val + 3, ptr - (opt->val + 3)); pattern[ptr - (opt->val + 3)] = 0; - re = pcre_compile2(pattern, 0, NULL, &pcre_err, &pcre_offset, NULL); + re = pcre2_compile((PCRE2_SPTR)pattern, PCRE2_ZERO_TERMINATED, 0, &pcre_err, &pcre_offset, NULL); _free(pattern); if (!re) continue; - r = pcre_exec(re, NULL, ifr.ifr_name, len, 0, 0, NULL, 0); - pcre_free(re); + pcre2_match_data *match_data = pcre2_match_data_create(0, NULL); + r = pcre2_match(re, (PCRE2_SPTR)ifr.ifr_name, len, 0, 0, match_data, NULL); + pcre2_match_data_free(match_data); + pcre2_code_free(re); if (r < 0) continue; @@ -1860,8 +1914,12 @@ static int __load_vlan_mon_re(int index, int flags, const char *name, int iflink long mask1[4096/8/sizeof(long)]; struct pppoe_serv_t *serv; - if (pcre_exec(arg->re, NULL, name, strlen(name), 0, 0, NULL, 0) < 0) + pcre2_match_data *match_data = pcre2_match_data_create(0, NULL); + if (pcre2_match(arg->re, (PCRE2_SPTR)name, strlen(name), 0, 0, match_data, NULL) < 0) { + pcre2_match_data_free(match_data); return 0; + } + pcre2_match_data_free(match_data); memset(&ifr, 0, sizeof(ifr)); strcpy(ifr.ifr_name, name); @@ -1894,11 +1952,11 @@ static int __load_vlan_mon_re(int index, int flags, const char *name, int iflink static void load_vlan_mon_re(const char *opt, long *mask, int len) { - pcre *re = NULL; - const char *pcre_err; + pcre2_code *re = NULL; + int pcre_err; char *pattern; const char *ptr; - int pcre_offset; + PCRE2_SIZE pcre_offset; struct iplink_arg arg; for (ptr = opt; *ptr && *ptr != ','; ptr++); @@ -1907,10 +1965,12 @@ static void load_vlan_mon_re(const char *opt, long *mask, int len) memcpy(pattern, opt + 3, ptr - (opt + 3)); pattern[ptr - (opt + 3)] = 0; - re = pcre_compile2(pattern, 0, NULL, &pcre_err, &pcre_offset, NULL); + re = pcre2_compile((PCRE2_SPTR)pattern, PCRE2_ZERO_TERMINATED, 0, &pcre_err, &pcre_offset, NULL); if (!re) { - log_error("pppoe: '%s': %s at %i\r\n", pattern, pcre_err, pcre_offset); + PCRE2_UCHAR err_msg[64]; + pcre2_get_error_message(pcre_err, err_msg, sizeof(err_msg)); + log_error("pppoe: '%s': %s at %i\r\n", pattern, err_msg, (int)pcre_offset); return; } @@ -1920,7 +1980,7 @@ static void load_vlan_mon_re(const char *opt, long *mask, int len) iplink_list((iplink_list_func)__load_vlan_mon_re, &arg); - pcre_free(re); + pcre2_code_free(re); _free(pattern); } diff --git a/accel-pppd/ctrl/pppoe/pppoe.h b/accel-pppd/ctrl/pppoe/pppoe.h index 7ba4ac63..42067590 100644 --- a/accel-pppd/ctrl/pppoe/pppoe.h +++ b/accel-pppd/ctrl/pppoe/pppoe.h @@ -6,8 +6,10 @@ #include <linux/if.h> #include <linux/if_pppox.h> +#include <openssl/md5.h> +#include <openssl/des.h> + #include "rbtree.h" -#include "crypto.h" /* PPPoE codes */ #define CODE_PADI 0x09 @@ -107,16 +109,24 @@ extern int conf_accept_any_service; extern char *conf_ac_name; extern char *conf_pado_delay; -extern unsigned int stat_starting; -extern unsigned int stat_active; -extern unsigned int stat_delayed_pado; -extern unsigned long stat_PADI_recv; -extern unsigned long stat_PADO_sent; -extern unsigned long stat_PADR_recv; -extern unsigned long stat_PADR_dup_recv; -extern unsigned long stat_PADS_sent; -extern unsigned long stat_PADI_drop; -extern unsigned long stat_filtered; +struct pppoe_stat_t +{ + unsigned int starting; + unsigned int active; + unsigned int delayed_PADO; + unsigned long PADI_recv; + unsigned long PADI_drop; + unsigned long PADO_sent; + unsigned long PADR_recv; + unsigned long PADR_dup_recv; + unsigned long PADS_sent; + unsigned long filtered; +}; + +void pppoe_stat_get(struct pppoe_stat_t *stat); +unsigned int pppoe_stat_starting(void); +unsigned int pppoe_stat_active(void); +void pppoe_stat_add_filtered(void); extern pthread_rwlock_t serv_lock; extern struct list_head serv_list; @@ -140,4 +150,3 @@ int tr101_send_access_request(struct pppoe_tag *tr101, struct rad_packet_t *pack int tr101_send_accounting_request(struct pppoe_tag *tr101, struct rad_packet_t *pack); #endif - diff --git a/accel-pppd/ctrl/pppoe/tr101.c b/accel-pppd/ctrl/pppoe/tr101.c index bb8b845a..06aeff86 100644 --- a/accel-pppd/ctrl/pppoe/tr101.c +++ b/accel-pppd/ctrl/pppoe/tr101.c @@ -8,6 +8,7 @@ #include "log.h" #include "radius.h" #include "memdebug.h" +#include "utils.h" #include "pppoe.h" @@ -75,85 +76,85 @@ static int tr101_send_request(struct pppoe_tag *tr101, struct rad_packet_t *pack case OPT_ACTUAL_DATA_RATE_UP: if (len != 4) goto inval; - if (rad_packet_add_int(pack, "ADSL-Forum", "Actual-Data-Rate-Upstream", ntohl(*(uint32_t *)ptr))) + if (rad_packet_add_int(pack, "ADSL-Forum", "Actual-Data-Rate-Upstream", u_read_be32(ptr))) return -1; break; case OPT_ACTUAL_DATA_RATE_DOWN: if (len != 4) goto inval; - if (rad_packet_add_int(pack, "ADSL-Forum", "Actual-Data-Rate-Downstream", ntohl(*(uint32_t *)ptr))) + if (rad_packet_add_int(pack, "ADSL-Forum", "Actual-Data-Rate-Downstream", u_read_be32(ptr))) return -1; break; case OPT_MIN_DATA_RATE_UP: if (len != 4) goto inval; - if (rad_packet_add_int(pack, "ADSL-Forum", "Minimum-Data-Rate-Upstream", ntohl(*(uint32_t *)ptr))) + if (rad_packet_add_int(pack, "ADSL-Forum", "Minimum-Data-Rate-Upstream", u_read_be32(ptr))) return -1; break; case OPT_MIN_DATA_RATE_DOWN: if (len != 4) goto inval; - if (rad_packet_add_int(pack, "ADSL-Forum", "Minimum-Data-Rate-Downstream", ntohl(*(uint32_t *)ptr))) + if (rad_packet_add_int(pack, "ADSL-Forum", "Minimum-Data-Rate-Downstream", u_read_be32(ptr))) return -1; break; case OPT_ATT_DATA_RATE_UP: if (len != 4) goto inval; - if (rad_packet_add_int(pack, "ADSL-Forum", "Attainable-Data-Rate-Upstream", ntohl(*(uint32_t *)ptr))) + if (rad_packet_add_int(pack, "ADSL-Forum", "Attainable-Data-Rate-Upstream", u_read_be32(ptr))) return -1; break; case OPT_ATT_DATA_RATE_DOWN: if (len != 4) goto inval; - if (rad_packet_add_int(pack, "ADSL-Forum", "Attainable-Data-Rate-Downstream", ntohl(*(uint32_t *)ptr))) + if (rad_packet_add_int(pack, "ADSL-Forum", "Attainable-Data-Rate-Downstream", u_read_be32(ptr))) return -1; break; case OPT_MAX_DATA_RATE_UP: if (len != 4) goto inval; - if (rad_packet_add_int(pack, "ADSL-Forum", "Maximum-Data-Rate-Upstream", ntohl(*(uint32_t *)ptr))) + if (rad_packet_add_int(pack, "ADSL-Forum", "Maximum-Data-Rate-Upstream", u_read_be32(ptr))) return -1; break; case OPT_MAX_DATA_RATE_DOWN: if (len != 4) goto inval; - if (rad_packet_add_int(pack, "ADSL-Forum", "Maximum-Data-Rate-Downstream", ntohl(*(uint32_t *)ptr))) + if (rad_packet_add_int(pack, "ADSL-Forum", "Maximum-Data-Rate-Downstream", u_read_be32(ptr))) return -1; break; case OPT_MIN_DATA_RATE_UP_LP: if (len != 4) goto inval; - if (rad_packet_add_int(pack, "ADSL-Forum", "Minimum-Data-Rate-Upstream-Low-Power", ntohl(*(uint32_t *)ptr))) + if (rad_packet_add_int(pack, "ADSL-Forum", "Minimum-Data-Rate-Upstream-Low-Power", u_read_be32(ptr))) return -1; break; case OPT_MIN_DATA_RATE_DOWN_LP: if (len != 4) goto inval; - if (rad_packet_add_int(pack, "ADSL-Forum", "Minimum-Data-Rate-Downstream-Low-Power", ntohl(*(uint32_t *)ptr))) + if (rad_packet_add_int(pack, "ADSL-Forum", "Minimum-Data-Rate-Downstream-Low-Power", u_read_be32(ptr))) return -1; break; case OPT_MAX_INTERL_DELAY_UP: if (len != 4) goto inval; - if (rad_packet_add_int(pack, "ADSL-Forum", "Maximum-Interleaving-Delay-Upstream", ntohl(*(uint32_t *)ptr))) + if (rad_packet_add_int(pack, "ADSL-Forum", "Maximum-Interleaving-Delay-Upstream", u_read_be32(ptr))) return -1; break; case OPT_ACTUAL_INTERL_DELAY_UP: if (len != 4) goto inval; - if (rad_packet_add_int(pack, "ADSL-Forum", "Actual-Interleaving-Delay-Upstream", ntohl(*(uint32_t *)ptr))) + if (rad_packet_add_int(pack, "ADSL-Forum", "Actual-Interleaving-Delay-Upstream", u_read_be32(ptr))) return -1; break; case OPT_MAX_INTER_DELAY_DOWN: if (len != 4) goto inval; - if (rad_packet_add_int(pack, "ADSL-Forum", "Maximum-Interleaving-Delay-Downstream", ntohl(*(uint32_t *)ptr))) + if (rad_packet_add_int(pack, "ADSL-Forum", "Maximum-Interleaving-Delay-Downstream", u_read_be32(ptr))) return -1; break; case OPT_ACTUAL_INTER_DELAY_DOWN: if (len != 4) goto inval; - if (rad_packet_add_int(pack, "ADSL-Forum", "Actual-Interleaving-Delay-Downstream", ntohl(*(uint32_t *)ptr))) + if (rad_packet_add_int(pack, "ADSL-Forum", "Actual-Interleaving-Delay-Downstream", u_read_be32(ptr))) return -1; break; case ACCESS_LOOP_ENCAP: diff --git a/accel-pppd/ctrl/pptp/pptp.c b/accel-pppd/ctrl/pptp/pptp.c index a95fe8ae..dac7c041 100644 --- a/accel-pppd/ctrl/pptp/pptp.c +++ b/accel-pppd/ctrl/pptp/pptp.c @@ -11,7 +11,7 @@ #include <netinet/in.h> #include <sys/socket.h> -#include "if_pppox.h" +#include <linux/if_pppox.h> #include "events.h" #include "list.h" @@ -25,6 +25,7 @@ #include "cli.h" #include "connlimit.h" +#include "pptp.h" #include "memdebug.h" @@ -55,6 +56,19 @@ struct pptp_conn_t struct ppp_t ppp; }; +struct pptp_stat_t +{ + unsigned int starting; + unsigned int active; +}; + +struct pptp_serv_t +{ + struct triton_context_t ctx; + struct triton_md_handler_t hnd; + struct pptp_stat_t stat; +}; + static int conf_ppp_max_mtu = PPTP_MAX_MTU; static int conf_timeout = 5; static int conf_echo_interval = 0; @@ -69,14 +83,53 @@ static const char *conf_ifname; static mempool_t conn_pool; -static unsigned int stat_starting; -static unsigned int stat_active; - static int pptp_read(struct triton_md_handler_t *h); static int pptp_write(struct triton_md_handler_t *h); static void pptp_timeout(struct triton_timer_t *); static void ppp_started(struct ap_session *); static void ppp_finished(struct ap_session *); +static void pptp_ctx_switch(struct triton_context_t *ctx, void *arg); +static int pptp_connect(struct triton_md_handler_t *h); +static void pptp_serv_close(struct triton_context_t *ctx); + +static struct pptp_serv_t serv = +{ + .hnd.read = pptp_connect, + .ctx.close = pptp_serv_close, + .ctx.before_switch = pptp_ctx_switch, +}; + +static void pptp_stat_inc(unsigned int *stat) +{ + __atomic_add_fetch(stat, 1, __ATOMIC_RELAXED); +} + +static void pptp_stat_dec(unsigned int *stat) +{ + __atomic_sub_fetch(stat, 1, __ATOMIC_RELAXED); +} + +static void pptp_stat_move(unsigned int *from, unsigned int *to) +{ + pptp_stat_dec(from); + pptp_stat_inc(to); +} + +static void pptp_stat_get(struct pptp_stat_t *stat) +{ + stat->starting = __atomic_load_n(&serv.stat.starting, __ATOMIC_RELAXED); + stat->active = __atomic_load_n(&serv.stat.active, __ATOMIC_RELAXED); +} + +unsigned int __export pptp_stat_starting(void) +{ + return __atomic_load_n(&serv.stat.starting, __ATOMIC_RELAXED); +} + +unsigned int __export pptp_stat_active(void) +{ + return __atomic_load_n(&serv.stat.active, __ATOMIC_RELAXED); +} static void pptp_ctx_switch(struct triton_context_t *ctx, void *arg) { @@ -101,11 +154,11 @@ static void disconnect(struct pptp_conn_t *conn) triton_timer_del(&conn->echo_timer); if (conn->state == STATE_PPP) { - __sync_sub_and_fetch(&stat_active, 1); + pptp_stat_dec(&serv.stat.active); conn->state = STATE_CLOSE; ap_session_terminate(&conn->ppp.ses, TERM_LOST_CARRIER, 1); } else if (conn->state != STATE_CLOSE) - __sync_sub_and_fetch(&stat_starting, 1); + pptp_stat_dec(&serv.stat.starting); triton_event_fire(EV_CTRL_FINISHED, &conn->ppp.ses); @@ -142,13 +195,14 @@ again: if (errno != EPIPE) { if (conf_verbose) log_ppp_info2("pptp: write: %s\n", strerror(errno)); - return -1; } + return -1; } } if ( n<size ) { memcpy(conn->out_buf, (uint8_t *)buf + n, size - n); + conn->out_size = size - n; triton_md_enable_handler(&conn->hnd, MD_MODE_WRITE); } @@ -306,7 +360,10 @@ static int pptp_out_call_rqst(struct pptp_conn_t *conn) src_addr.sa_protocol = PX_PROTO_PPTP; src_addr.sa_addr.pptp.call_id = 0; addrlen = sizeof(addr); - getsockname(conn->hnd.fd, (struct sockaddr*)&addr, &addrlen); + if (getsockname(conn->hnd.fd, (struct sockaddr*)&addr, &addrlen)) { + log_ppp_error("pptp: getsockname: %s\n", strerror(errno)); + return -1; + } src_addr.sa_addr.pptp.sin_addr = addr.sin_addr; memset(&dst_addr, 0, sizeof(dst_addr)); @@ -314,7 +371,10 @@ static int pptp_out_call_rqst(struct pptp_conn_t *conn) dst_addr.sa_protocol = PX_PROTO_PPTP; dst_addr.sa_addr.pptp.call_id = htons(msg->call_id); addrlen = sizeof(addr); - getpeername(conn->hnd.fd, (struct sockaddr*)&addr, &addrlen); + if (getpeername(conn->hnd.fd, (struct sockaddr*)&addr, &addrlen)) { + log_ppp_error("pptp: getpeername: %s\n", strerror(errno)); + return -1; + } dst_addr.sa_addr.pptp.sin_addr = addr.sin_addr; pptp_sock = socket(AF_PPPOX, SOCK_STREAM, PX_PROTO_PPTP); @@ -331,7 +391,11 @@ static int pptp_out_call_rqst(struct pptp_conn_t *conn) return -1; } addrlen = sizeof(src_addr); - getsockname(pptp_sock, (struct sockaddr*)&src_addr, &addrlen); + if (getsockname(pptp_sock, (struct sockaddr*)&src_addr, &addrlen)) { + log_ppp_error("pptp: getsockname: %s\n", strerror(errno)); + close(pptp_sock); + return -1; + } if (connect(pptp_sock, (struct sockaddr*)&dst_addr, sizeof(dst_addr))) { log_ppp_error("failed to connect PPTP socket (%s)\n", strerror(errno)); @@ -339,11 +403,13 @@ static int pptp_out_call_rqst(struct pptp_conn_t *conn) return -1; } - if (send_pptp_out_call_rply(conn, msg, src_addr.sa_addr.pptp.call_id, PPTP_CALL_RES_OK, 0)) + if (send_pptp_out_call_rply(conn, msg, src_addr.sa_addr.pptp.call_id, PPTP_CALL_RES_OK, 0)) { + close(pptp_sock); return -1; + } conn->call_id = src_addr.sa_addr.pptp.call_id; - conn->peer_call_id = msg->call_id; + conn->peer_call_id = ntohs(msg->call_id); conn->ppp.fd = pptp_sock; conn->ppp.ses.chan_name = _strdup(inet_ntoa(dst_addr.sa_addr.pptp.sin_addr)); @@ -356,8 +422,7 @@ static int pptp_out_call_rqst(struct pptp_conn_t *conn) return -1; } conn->state = STATE_PPP; - __sync_sub_and_fetch(&stat_starting, 1); - __sync_add_and_fetch(&stat_active, 1); + pptp_stat_move(&serv.stat.starting, &serv.stat.active); if (conn->timeout_timer.tpd) triton_timer_del(&conn->timeout_timer); @@ -397,7 +462,7 @@ static int pptp_call_clear_rqst(struct pptp_conn_t *conn) triton_timer_del(&conn->echo_timer); if (conn->state == STATE_PPP) { - __sync_sub_and_fetch(&stat_active, 1); + pptp_stat_dec(&serv.stat.active); conn->state = STATE_CLOSE; ap_session_terminate(&conn->ppp.ses, TERM_USER_REQUEST, 1); } @@ -443,7 +508,7 @@ static void pptp_send_echo(struct triton_timer_t *t) .header = PPTP_HEADER_CTRL(PPTP_ECHO_RQST), }; - if (++conn->echo_sent == conf_echo_failure) { + if (conf_echo_failure && ++conn->echo_sent >= conf_echo_failure) { log_ppp_warn("pptp: no echo reply\n"); disconnect(conn); return; @@ -514,6 +579,10 @@ static int pptp_read(struct triton_md_handler_t *h) log_ppp_error("pptp: invalid magic\n"); goto drop; } + if (ntohs(hdr->length) < sizeof(*hdr)) { + log_ppp_error("pptp: message is too short\n"); + goto drop; + } if (ntohs(hdr->length) >= PPTP_CTRL_SIZE_MAX) { log_ppp_error("pptp: message is too long\n"); goto drop; @@ -578,7 +647,7 @@ static void pptp_close(struct triton_context_t *ctx) { struct pptp_conn_t *conn = container_of(ctx, typeof(*conn), ctx); if (conn->state == STATE_PPP) { - __sync_sub_and_fetch(&stat_active, 1); + pptp_stat_dec(&serv.stat.active); conn->state = STATE_CLOSE; ap_session_terminate(&conn->ppp.ses, TERM_ADMIN_RESET, 1); if (send_pptp_call_disconnect_notify(conn, 3)) { @@ -609,7 +678,7 @@ static void ppp_finished(struct ap_session *ses) if (conn->state != STATE_CLOSE) { log_ppp_debug("pptp: ppp finished\n"); conn->state = STATE_CLOSE; - __sync_sub_and_fetch(&stat_active, 1); + pptp_stat_dec(&serv.stat.active); if (send_pptp_call_disconnect_notify(conn, 3)) triton_context_call(&conn->ctx, (void (*)(void*))disconnect, conn); @@ -626,20 +695,15 @@ static void ppp_finished(struct ap_session *ses) //================================== -struct pptp_serv_t -{ - struct triton_context_t ctx; - struct triton_md_handler_t hnd; -}; - static int pptp_connect(struct triton_md_handler_t *h) { - struct sockaddr_in addr; - socklen_t size = sizeof(addr); + struct sockaddr_in addr, laddr; + socklen_t size; int sock; struct pptp_conn_t *conn; while(1) { + size = sizeof(addr); sock = accept(h->fd, (struct sockaddr *)&addr, &size); if (sock < 0) { if (errno == EAGAIN) @@ -653,12 +717,12 @@ static int pptp_connect(struct triton_md_handler_t *h) continue; } - if (conf_max_starting && ap_session_stat.starting >= conf_max_starting) { + if (conf_max_starting && ap_session_stat_starting() >= conf_max_starting) { close(sock); continue; } - if (conf_max_sessions && ap_session_stat.active + ap_session_stat.starting >= conf_max_sessions) { + if (conf_max_sessions && ap_session_stat_active() + ap_session_stat_starting() >= conf_max_sessions) { close(sock); continue; } @@ -676,6 +740,13 @@ static int pptp_connect(struct triton_md_handler_t *h) continue; } + size = sizeof(laddr); + if (getsockname(sock, (struct sockaddr *)&laddr, &size)) { + log_error("pptp: getsockname: %s, closing connection...\n", strerror(errno)); + close(sock); + continue; + } + if (fcntl(sock, F_SETFL, O_NONBLOCK)) { log_error("pptp: failed to set nonblocking mode: %s, closing connection...\n", strerror(errno)); close(sock); @@ -708,8 +779,7 @@ static int pptp_connect(struct triton_md_handler_t *h) conn->ctrl.calling_station_id = _malloc(17); conn->ctrl.called_station_id = _malloc(17); u_inet_ntoa(addr.sin_addr.s_addr, conn->ctrl.calling_station_id); - getsockname(sock, &addr, &size); - u_inet_ntoa(addr.sin_addr.s_addr, conn->ctrl.called_station_id); + u_inet_ntoa(laddr.sin_addr.s_addr, conn->ctrl.called_station_id); ppp_init(&conn->ppp); conn->ppp.ses.ctrl = &conn->ctrl; @@ -733,7 +803,7 @@ static int pptp_connect(struct triton_md_handler_t *h) triton_event_fire(EV_CTRL_STARTING, &conn->ppp.ses); - __sync_add_and_fetch(&stat_starting, 1); + pptp_stat_inc(&serv.stat.starting); } return 0; } @@ -744,28 +814,19 @@ static void pptp_serv_close(struct triton_context_t *ctx) triton_context_unregister(ctx); } -static struct pptp_serv_t serv= -{ - .hnd.read = pptp_connect, - .ctx.close = pptp_serv_close, - .ctx.before_switch = pptp_ctx_switch, -}; - static int show_stat_exec(const char *cmd, char * const *fields, int fields_cnt, void *client) { + struct pptp_stat_t stat; + + pptp_stat_get(&stat); + cli_send(client, "pptp:\r\n"); - cli_sendv(client," starting: %u\r\n", stat_starting); - cli_sendv(client," active: %u\r\n", stat_active); + cli_sendv(client," starting: %u\r\n", stat.starting); + cli_sendv(client," active: %u\r\n", stat.active); return CLI_CMD_OK; } -void __export pptp_get_stat(unsigned int **starting, unsigned int **active) -{ - *starting = &stat_starting; - *active = &stat_active; -} - static void load_config(void) { char *opt; @@ -834,7 +895,7 @@ static void pptp_init(void) { struct sockaddr_in addr; char *opt; - int fd; + int fd, f = 1; fd = socket(AF_PPPOX, SOCK_STREAM, PX_PROTO_PPTP); if (fd >= 0) @@ -850,12 +911,17 @@ static void pptp_init(void) fcntl(serv.hnd.fd, F_SETFD, fcntl(serv.hnd.fd, F_GETFD) | FD_CLOEXEC); + memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; opt = conf_get_opt("pptp", "bind"); - if (opt) - addr.sin_addr.s_addr = inet_addr(opt); - else + if (opt) { + if (!inet_aton(opt, &addr.sin_addr)) { + log_emerg("pptp: failed to parse bind address '%s'\n", opt); + close(serv.hnd.fd); + return; + } + } else addr.sin_addr.s_addr = htonl(INADDR_ANY); opt = conf_get_opt("pptp", "port"); @@ -864,7 +930,7 @@ static void pptp_init(void) else addr.sin_port = htons(PPTP_PORT); - setsockopt(serv.hnd.fd, SOL_SOCKET, SO_REUSEADDR, &serv.hnd.fd, 4); + setsockopt(serv.hnd.fd, SOL_SOCKET, SO_REUSEADDR, &f, sizeof(f)); if (bind (serv.hnd.fd, (struct sockaddr *) &addr, sizeof (addr)) < 0) { log_emerg("pptp: failed to bind socket: %s\n", strerror(errno)); close(serv.hnd.fd); diff --git a/accel-pppd/ctrl/pptp/pptp.h b/accel-pppd/ctrl/pptp/pptp.h new file mode 100644 index 00000000..29f24ca2 --- /dev/null +++ b/accel-pppd/ctrl/pptp/pptp.h @@ -0,0 +1,7 @@ +#ifndef __PPTP_H +#define __PPTP_H + +unsigned int pptp_stat_starting(void); +unsigned int pptp_stat_active(void); + +#endif diff --git a/accel-pppd/ctrl/pptp/pptp_prot.h b/accel-pppd/ctrl/pptp/pptp_prot.h index ee8cb341..3726334a 100644 --- a/accel-pppd/ctrl/pptp/pptp_prot.h +++ b/accel-pppd/ctrl/pptp/pptp_prot.h @@ -281,7 +281,6 @@ struct pptp_set_link_info /* for control message type 15 */ (type==PPTP_WAN_ERR_NTFY )?sizeof(struct pptp_wan_err_ntfy): \ (type==PPTP_SET_LINK_INFO )?sizeof(struct pptp_set_link_info): \ 0) -#define max(a,b) (((a)>(b))?(a):(b)) #define PPTP_CTRL_SIZE_MAX ( \ max(sizeof(struct pptp_start_ctrl_conn), \ max(sizeof(struct pptp_echo_rqst), \ diff --git a/accel-pppd/ctrl/sstp/CMakeLists.txt b/accel-pppd/ctrl/sstp/CMakeLists.txt index 3cb1799c..3a79526f 100644 --- a/accel-pppd/ctrl/sstp/CMakeLists.txt +++ b/accel-pppd/ctrl/sstp/CMakeLists.txt @@ -2,6 +2,6 @@ INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) ADD_LIBRARY(sstp SHARED sstp.c) -TARGET_LINK_LIBRARIES(sstp util) +TARGET_LINK_LIBRARIES(sstp util ${crypto_lib}) INSTALL(TARGETS sstp LIBRARY DESTINATION lib${LIB_SUFFIX}/accel-ppp) diff --git a/accel-pppd/ctrl/sstp/if_ppposeq.h b/accel-pppd/ctrl/sstp/if_ppposeq.h new file mode 120000 index 00000000..f525ccdb --- /dev/null +++ b/accel-pppd/ctrl/sstp/if_ppposeq.h @@ -0,0 +1 @@ +../../../drivers/ppposeq/ppposeq.h
\ No newline at end of file diff --git a/accel-pppd/ctrl/sstp/sstp.c b/accel-pppd/ctrl/sstp/sstp.c index 2e2c4d3b..0920f5af 100644 --- a/accel-pppd/ctrl/sstp/sstp.c +++ b/accel-pppd/ctrl/sstp/sstp.c @@ -4,6 +4,7 @@ #include <stdarg.h> #include <errno.h> #include <string.h> +#include <inttypes.h> #include <fcntl.h> #include <time.h> #include <termios.h> @@ -19,10 +20,17 @@ #include <sys/stat.h> #include "linux_ppp.h" -#ifdef CRYPTO_OPENSSL +/* + * Suppress OpenSSL 3.0 deprecation warnings for the DH API: it is deprecated + * but still functional, and still required for protocol compatibility. The + * project sets this for every target (see the top level CMakeLists.txt); it + * is repeated here because it only takes effect if it is defined before the + * first OpenSSL header is pulled in. + */ +#define OPENSSL_API_COMPAT 0x10100000L #include <openssl/ssl.h> -#include <openssl/err.h> -#endif +#include <openssl/dh.h> +#include <openssl/err.h> #include "triton.h" #include "events.h" @@ -38,16 +46,10 @@ #include "memdebug.h" #include "proxy_prot.h" +#include "sstp.h" #include "sstp_prot.h" +#include "if_ppposeq.h" -#ifndef min -#define min(x,y) ((x) < (y) ? (x) : (y)) -#endif -#ifndef max -#define max(x,y) ((x) > (y) ? (x) : (y)) -#endif - -#define PPP_SYNC 0 /* buggy yet */ #define PPP_BUF_SIZE 8192 #define PPP_BUF_IOVEC 256 #define PPP_F_ESCAPE 1 @@ -71,6 +73,20 @@ enum { STATE_FINISHED, }; +enum { + HTTP_ERR_ALLOW = -1, + HTTP_ERR_DENY = 0, + HTTP_ERR_REDIRECT = 1, + HTTP_ERR_REDIRECT_APPEND = 2, +}; + +/* seqpacket needs the ppposeq module, async is the legacy pty path */ +enum { + PPP_MODE_AUTO = -1, + PPP_MODE_ASYNC = 0, + PPP_MODE_SEQPACKET = 1, +}; + struct sockaddr_t { socklen_t len; union { @@ -102,9 +118,7 @@ struct buffer_t { struct sstp_stream_t { union { int fd; -#ifdef CRYPTO_OPENSSL SSL *ssl; -#endif }; ssize_t (*read)(struct sstp_stream_t *stream, void *buf, size_t count); ssize_t (*recv)(struct sstp_stream_t *stream, void *buf, size_t count, int flags); @@ -139,6 +153,7 @@ struct sstp_conn_t { int ppp_state; int ppp_flags; + int ppp_mode; struct buffer_t *ppp_in; struct list_head ppp_queue; @@ -147,21 +162,23 @@ struct sstp_conn_t { struct ap_ctrl ctrl; }; -static struct sstp_serv_t { +struct sstp_serv_t { struct triton_context_t ctx; struct triton_md_handler_t hnd; struct sockaddr_t addr; -#ifdef CRYPTO_OPENSSL SSL_CTX *ssl_ctx; -#endif -} serv; + struct sstp_stat_t stat; +}; + +static struct sstp_serv_t serv; static int conf_timeout = SSTP_NEGOTIOATION_TIMEOUT; static int conf_hello_interval = SSTP_HELLO_TIMEOUT; static int conf_verbose = 0; static int conf_ppp_max_mtu = 1452; +static int conf_ppp_mode = PPP_MODE_AUTO; static const char *conf_ip_pool; static const char *conf_ipv6_pool; static const char *conf_dpv6_pool; @@ -176,14 +193,11 @@ static struct hash_t conf_hash_sha1 = { .len = 0 }; static struct hash_t conf_hash_sha256 = { .len = 0 }; //static int conf_bypass_auth = 0; static const char *conf_hostname = NULL; -static int conf_http_mode = -1; +static int conf_http_mode = HTTP_ERR_ALLOW; static const char *conf_http_url = NULL; static mempool_t conn_pool; -static unsigned int stat_starting; -static unsigned int stat_active; - static inline void sstp_queue(struct sstp_conn_t *conn, struct buffer_t *buf); static int sstp_send(struct sstp_conn_t *conn, struct buffer_t *buf); static inline void sstp_queue_deferred(struct sstp_conn_t *conn, struct buffer_t *buf); @@ -193,6 +207,38 @@ static void sstp_disconnect(struct sstp_conn_t *conn); static int sstp_handler(struct sstp_conn_t *conn, struct buffer_t *buf); static int http_handler(struct sstp_conn_t *conn, struct buffer_t *buf); +void __export sstp_stat_get(struct sstp_stat_t *stat) +{ + stat->starting = __atomic_load_n(&serv.stat.starting, __ATOMIC_RELAXED); + stat->active = __atomic_load_n(&serv.stat.active, __ATOMIC_RELAXED); +} + +unsigned int __export sstp_stat_starting(void) +{ + return __atomic_load_n(&serv.stat.starting, __ATOMIC_RELAXED); +} + +unsigned int __export sstp_stat_active(void) +{ + return __atomic_load_n(&serv.stat.active, __ATOMIC_RELAXED); +} + +static void sstp_stat_inc(unsigned int *stat) +{ + __atomic_add_fetch(stat, 1, __ATOMIC_RELAXED); +} + +static void sstp_stat_dec(unsigned int *stat) +{ + __atomic_sub_fetch(stat, 1, __ATOMIC_RELAXED); +} + +static void sstp_stat_move(unsigned int *from, unsigned int *to) +{ + sstp_stat_dec(from); + sstp_stat_inc(to); +} + /* * FCS lookup table as calculated by genfcstab. */ @@ -489,7 +535,6 @@ static struct sstp_stream_t *stream_init(int fd) /* ssl stream */ -#ifdef CRYPTO_OPENSSL static ssize_t ssl_stream_read(struct sstp_stream_t *stream, void *buf, size_t count) { int ret, err; @@ -585,7 +630,6 @@ error: ssl_stream_free(stream); return NULL; } -#endif /* proxy */ @@ -830,7 +874,7 @@ static char *http_getvalue(char *line, const char *name, int len) return sep ? line : NULL; } -static int http_send_response(struct sstp_conn_t *conn, char *proto, char *status, char *headers) +static int http_send_response(struct sstp_conn_t *conn, char *proto, char *status, char *headers, u_int64_t length) { char datetime[sizeof("aaa, dd bbb yyyy HH:MM:SS GMT")]; char linebuf[1024], *line; @@ -843,7 +887,12 @@ static int http_send_response(struct sstp_conn_t *conn, char *proto, char *statu /* "Server: %s\r\n" */ "Date: %s\r\n" "%s" - "\r\n", proto, status, /* "accel-ppp",*/ datetime, headers ? : ""); + "Content-Length: %" PRIu64 "\r\n" + "Connection: %s\r\n" + "\r\n", + proto, status, /* "accel-ppp",*/ datetime, + headers ? : "", + length, length ? "keep-alive" : "close"); if (!buf) { log_error("sstp: no memory\n"); return -1; @@ -879,18 +928,18 @@ static int http_recv_request(struct sstp_conn_t *conn, uint8_t *data, int len) log_ppp_info2("recv [HTTP <%s>]\n", line); if (vstrsep(line, " ", &method, &request, &proto) < 3) { - if (conf_http_mode) - http_send_response(conn, "HTTP/1.1", "400 Bad Request", NULL); + if (conf_http_mode != HTTP_ERR_DENY) + http_send_response(conn, "HTTP/1.1", "400 Bad Request", NULL, 0); return -1; } if (strncasecmp(proto, "HTTP/1", sizeof("HTTP/1") - 1) != 0) { - if (conf_http_mode) - http_send_response(conn, "HTTP/1.1", "400 Bad Request", NULL); + if (conf_http_mode != HTTP_ERR_DENY) + http_send_response(conn, "HTTP/1.1", "400 Bad Request", NULL, 0); return -1; } if (strcasecmp(method, SSTP_HTTP_METHOD) != 0 && strcasecmp(method, "GET") != 0) { - if (conf_http_mode) - http_send_response(conn, proto, "501 Not Implemented", NULL); + if (conf_http_mode != HTTP_ERR_DENY) + http_send_response(conn, proto, "501 Not Implemented", NULL, 0); return -1; } @@ -911,25 +960,24 @@ static int http_recv_request(struct sstp_conn_t *conn, uint8_t *data, int len) } if (host_error) { - if (conf_http_mode) - http_send_response(conn, proto, "404 Not Found", NULL); + if (conf_http_mode != HTTP_ERR_DENY) + http_send_response(conn, proto, "404 Not Found", NULL, 0); return -1; } if (strcasecmp(method, SSTP_HTTP_METHOD) != 0 || strcasecmp(request, SSTP_HTTP_URI) != 0) { if (conf_http_mode > 0) { if (_asprintf(&line, "Location: %s%s\r\n", - conf_http_url, (conf_http_mode == 2) ? request : "") < 0) + conf_http_url, (conf_http_mode == HTTP_ERR_REDIRECT_APPEND) ? request : "") < 0) return -1; - http_send_response(conn, proto, "301 Moved Permanently", line); + http_send_response(conn, proto, "301 Moved Permanently", line, 0); _free(line); - } else if (conf_http_mode < 0) - http_send_response(conn, proto, "404 Not Found", NULL); + } else if (conf_http_mode == HTTP_ERR_ALLOW) + http_send_response(conn, proto, "404 Not Found", NULL, 0); return -1; } - return http_send_response(conn, proto, "200 OK", - "Content-Length: 18446744073709551615\r\n"); + return http_send_response(conn, proto, "200 OK", NULL, -1); } static int http_handler(struct sstp_conn_t *conn, struct buffer_t *buf) @@ -1006,24 +1054,12 @@ static int ppp_allocate_pty(int *master, int *slave, int flags) goto error; } -#if PPP_SYNC - value = N_SYNC_PPP; -#else value = N_PPP; -#endif if (ioctl(sfd, TIOCSETD, &value) < 0) { log_ppp_error("sstp: ppp: set pty line discipline: %s\n", strerror(errno)); goto error; } -#if PPP_SYNC - value = N_HDLC; - if (ioctl(mfd, TIOCSETD, &value) < 0) { - log_ppp_error("sstp: ppp: set pty line discipline: %s\n", strerror(errno)); - goto error; - } -#endif - if ((value = fcntl(mfd, F_GETFL)) < 0 || fcntl(mfd, F_SETFL, value | flags) < 0 || (value = fcntl(sfd, F_GETFL)) < 0 || fcntl(sfd, F_SETFL, value | flags) < 0) { log_ppp_error("sstp: ppp: set pty status flags: %s\n", strerror(errno)); @@ -1040,6 +1076,59 @@ error: return -1; } +/* + * ppposeq channel: the socket is both the ppp endpoint we exchange frames + * on and the fd establish_ppp() gets the channel from, as pppox_ioctl + * answers PPPIOCGCHAN on it. One datagram is one frame, so no framing. + */ +static int ppp_allocate_seq(int *master, int *slave, int flags) +{ + struct sockaddr_ppposeq sa = { + .sa_family = AF_PPPOX, + .sa_protocol = PX_PROTO_OSEQ, + }; + int value, mfd, sfd; + + mfd = socket(AF_PPPOX, SOCK_SEQPACKET, PX_PROTO_OSEQ); + if (mfd < 0) { + log_ppp_error("sstp: ppp: create socket: %s\n", strerror(errno)); + return -1; + } + + if (connect(mfd, (struct sockaddr *)&sa, sizeof(sa)) < 0) { + log_ppp_error("sstp: ppp: connect socket: %s\n", strerror(errno)); + goto error_mfd; + } + + sfd = dup(mfd); + if (sfd < 0) { + log_ppp_error("sstp: ppp: dup socket: %s\n", strerror(errno)); + goto error_mfd; + } + + if (flags & O_CLOEXEC) { + fcntl(mfd, F_SETFD, fcntl(mfd, F_GETFD) | FD_CLOEXEC); + fcntl(sfd, F_SETFD, fcntl(sfd, F_GETFD) | FD_CLOEXEC); + flags &= ~O_CLOEXEC; + } + + /* status flags are inherited by shared file descriptors */ + if ((value = fcntl(mfd, F_GETFL)) < 0 || fcntl(mfd, F_SETFL, value | flags) < 0) { + log_ppp_error("sstp: ppp: set socket status flags: %s\n", strerror(errno)); + goto error; + } + + *master = mfd; + *slave = sfd; + return 0; + +error: + close(sfd); +error_mfd: + close(mfd); + return -1; +} + static void ppp_started(struct ap_session *ses) { struct ppp_t *ppp = container_of(ses, typeof(*ppp), ses); @@ -1073,18 +1162,16 @@ static void ppp_finished(struct ap_session *ses) } } -static int ppp_read(struct triton_md_handler_t *h) +static int ppp_read_pty(struct triton_md_handler_t *h) { struct sstp_conn_t *conn = container_of(h, typeof(*conn), ppp_hnd); struct buffer_t *buf; struct sstp_hdr *hdr; uint8_t pppbuf[PPP_BUF_SIZE], *src; int i, n; -#if !PPP_SYNC uint8_t byte; buf = conn->ppp_in; -#endif while (1) { n = read(h->fd, pppbuf, sizeof(pppbuf)); if (n < 0) { @@ -1109,29 +1196,7 @@ static int ppp_read(struct triton_md_handler_t *h) } src = pppbuf; -#if PPP_SYNC - while (n > 0) { - if (src[0] == PPP_ALLSTATIONS) - i = conn->ppp.mtu + 4 - (src[2] & 1); - else - i = conn->ppp.mtu + 2 - (src[0] & 1); - if (i > n) - i = n; - buf = alloc_buf(i + sizeof(*hdr)); - if (!buf) { - log_ppp_error("sstp: ppp: no memory\n"); - goto drop; - } - hdr = buf_put(buf, sizeof(*hdr)); - buf_put_data(buf, src, i); - INIT_SSTP_DATA_HDR(hdr, buf->len); - sstp_queue(conn, buf); - - n -= i; - src += i; - } -#else if (!buf) { alloc: conn->ppp_in = buf = alloc_buf(SSTP_MAX_PACKET_SIZE + PPP_FCSLEN); @@ -1167,6 +1232,11 @@ static int ppp_read(struct triton_md_handler_t *h) switch (byte) { case PPP_FLAG: if (buf->len <= PPP_FCSLEN || conn->ppp_flags) { + /* skip idle flag */ + if (buf->len == 0 && conn->ppp_flags == 0) + break; + if (conf_verbose) + log_ppp_info2("sstp: ppp: read: malformed packet\n"); buf_set_length(buf, 0); conn->ppp_flags = 0; break; @@ -1181,7 +1251,6 @@ static int ppp_read(struct triton_md_handler_t *h) break; } } -#endif } if (!list_empty(&conn->out_queue)) triton_md_enable_handler(&conn->hnd, MD_MODE_WRITE); @@ -1192,7 +1261,65 @@ drop: return 1; } -static int ppp_write(struct triton_md_handler_t *h) +static int ppp_read_seq(struct triton_md_handler_t *h) +{ + struct sstp_conn_t *conn = container_of(h, typeof(*conn), ppp_hnd); + struct buffer_t *buf; + struct sstp_hdr *hdr; + int n; + + buf = conn->ppp_in; + while (1) { + if (!buf) { + alloc: + conn->ppp_in = buf = alloc_buf(conn->ppp.mtu ? + conn->ppp.mtu + PPP_HDRLEN + sizeof(*hdr) : + SSTP_MAX_PACKET_SIZE); + if (!buf) { + log_ppp_error("sstp: ppp: no memory\n"); + goto drop; + } + buf_reserve(buf, sizeof(*hdr)); + } + + n = recv(h->fd, buf->tail, buf_tailroom(buf), MSG_TRUNC); + if (n < 0) { + if (errno == EINTR) + continue; + if (errno == EAGAIN) + break; + log_ppp_error("sstp: ppp: recv: %s\n", strerror(errno)); + goto drop; + } else if (n > buf_tailroom(buf)) { + if (conf_verbose) + log_ppp_info2("sstp: ppp: recv: too long packet\n"); + continue; + } + + switch (conn->sstp_state) { + case STATE_SERVER_CALL_CONNECTED_PENDING: + case STATE_SERVER_CALL_CONNECTED: + break; + default: + continue; + } + + buf_put(buf, n); + hdr = buf_push(buf, sizeof(*hdr)); + INIT_SSTP_DATA_HDR(hdr, buf->len); + sstp_queue(conn, buf); + goto alloc; + } + if (!list_empty(&conn->out_queue)) + triton_md_enable_handler(&conn->hnd, MD_MODE_WRITE); + return 0; + +drop: + sstp_disconnect(conn); + return 1; +} + +static int ppp_write_pty(struct triton_md_handler_t *h) { struct sstp_conn_t *conn = container_of(h, typeof(*conn), ppp_hnd); struct iovec iov[PPP_BUF_IOVEC]; @@ -1200,7 +1327,7 @@ static int ppp_write(struct triton_md_handler_t *h) ssize_t n; int i; - if (!list_empty(&conn->ppp_queue)) { + while (!list_empty(&conn->ppp_queue)) { i = n = 0; list_for_each_entry(buf, &conn->ppp_queue, entry) { if (i < PPP_BUF_IOVEC && n < PPP_BUF_SIZE) { @@ -1217,8 +1344,9 @@ static int ppp_write(struct triton_md_handler_t *h) goto again; if (errno == EAGAIN) goto defer; - if (conf_verbose && errno != EPIPE) - log_ppp_info2("sstp: ppp: write: %s\n", strerror(errno)); + if (errno == EPIPE) + goto drop; + log_ppp_error("sstp: ppp: write: %s\n", strerror(errno)); goto drop; } else if (n == 0) goto defer; @@ -1232,9 +1360,43 @@ static int ppp_write(struct triton_md_handler_t *h) list_del(&buf->entry); free_buf(buf); } while (n > 0); + } + triton_md_disable_handler(h, MD_MODE_WRITE); + return 0; - if (!list_empty(&conn->ppp_queue)) - goto defer; +defer: + triton_md_enable_handler(h, MD_MODE_WRITE); + return 0; + +drop: + triton_context_call(&conn->ctx, (triton_event_func)sstp_disconnect, conn); + return 1; +} + +static int ppp_write_seq(struct triton_md_handler_t *h) +{ + struct sstp_conn_t *conn = container_of(h, typeof(*conn), ppp_hnd); + struct buffer_t *buf; + ssize_t n; + + while (!list_empty(&conn->ppp_queue)) { + buf = list_first_entry(&conn->ppp_queue, typeof(*buf), entry); + again: + n = send(conn->ppp_hnd.fd, buf->head, buf->len, 0); + if (n < 0) { + if (errno == EINTR) + goto again; + if (errno == EAGAIN) + goto defer; + log_ppp_error("sstp: ppp: send: %s\n", strerror(errno)); + goto drop; + } else if (n < buf->len) { + log_ppp_error("sstp: ppp: send: too short packet\n"); + goto drop; + } + + list_del(&buf->entry); + free_buf(buf); } triton_md_disable_handler(h, MD_MODE_WRITE); return 0; @@ -1482,13 +1644,25 @@ static int sstp_recv_msg_call_connect_request(struct sstp_conn_t *conn, struct s return sstp_send_msg_call_connect_nak(conn); } - if (ppp_allocate_pty(&master, &slave, O_CLOEXEC | O_NONBLOCK) < 0) + switch (conn->ppp_mode) { + case PPP_MODE_ASYNC: + if (ppp_allocate_pty(&master, &slave, O_CLOEXEC | O_NONBLOCK) < 0) + return -1; + conn->ppp_hnd.read = ppp_read_pty; + conn->ppp_hnd.write = ppp_write_pty; + break; + case PPP_MODE_SEQPACKET: + if (ppp_allocate_seq(&master, &slave, O_CLOEXEC | O_NONBLOCK) < 0) + return -1; + conn->ppp_hnd.read = ppp_read_seq; + conn->ppp_hnd.write = ppp_write_seq; + break; + default: + log_ppp_error("sstp: invalid ppp-mode\n"); return -1; + } conn->ppp_hnd.fd = master; - conn->ppp_hnd.read = ppp_read; - conn->ppp_hnd.write = ppp_write; - triton_md_register_handler(&conn->ctx, &conn->ppp_hnd); triton_md_enable_handler(&conn->ppp_hnd, MD_MODE_READ); @@ -1500,8 +1674,7 @@ static int sstp_recv_msg_call_connect_request(struct sstp_conn_t *conn, struct s goto error; conn->sstp_state = STATE_SERVER_CALL_CONNECTED_PENDING; - __sync_sub_and_fetch(&stat_starting, 1); - __sync_add_and_fetch(&stat_active, 1); + sstp_stat_move(&serv.stat.starting, &serv.stat.active); triton_event_fire(EV_CTRL_STARTED, &conn->ppp.ses); conn->ppp_state = STATE_STARTING; @@ -1528,12 +1701,10 @@ static int sstp_recv_msg_call_connected(struct sstp_conn_t *conn, struct sstp_ct uint8_t hash; unsigned int len; struct npioctl np; -#ifdef CRYPTO_OPENSSL typeof(*msg) buf; uint8_t md[EVP_MAX_MD_SIZE], *ptr; const EVP_MD *evp; unsigned int mdlen; -#endif if (conf_verbose) log_ppp_info2("recv [SSTP SSTP_MSG_CALL_CONNECTED]\n"); @@ -1571,9 +1742,7 @@ static int sstp_recv_msg_call_connected(struct sstp_conn_t *conn, struct sstp_ct log_ppp_error("sstp: invalid SHA256 Cert Hash\n"); return sstp_abort(conn, 0); } -#ifdef CRYPTO_OPENSSL evp = EVP_sha256(); -#endif } else if (hash & CERT_HASH_PROTOCOL_SHA1) { len = SHA_DIGEST_LENGTH; if (conf_hash_sha1.len == len && @@ -1581,9 +1750,7 @@ static int sstp_recv_msg_call_connected(struct sstp_conn_t *conn, struct sstp_ct log_ppp_error("sstp: invalid SHA1 Cert Hash\n"); return sstp_abort(conn, 0); } -#ifdef CRYPTO_OPENSSL evp = EVP_sha1(); -#endif } else { log_ppp_error("sstp: invalid Hash Protocol 0x%02x\n", msg->attr.hash_protocol_bitmask); @@ -1608,7 +1775,6 @@ static int sstp_recv_msg_call_connected(struct sstp_conn_t *conn, struct sstp_ct return 0; } -#ifdef CRYPTO_OPENSSL ptr = mempcpy(md, SSTP_CMK_SEED, SSTP_CMK_SEED_SIZE); *ptr++ = len; *ptr++ = 0; @@ -1624,7 +1790,6 @@ static int sstp_recv_msg_call_connected(struct sstp_conn_t *conn, struct sstp_ct log_ppp_error("sstp: invalid Compound MAC\n"); return sstp_abort(conn, 0); } -#endif } if (conn->timeout_timer.tpd) @@ -1791,11 +1956,9 @@ static int sstp_recv_data_packet(struct sstp_conn_t *conn, struct sstp_hdr *hdr) { struct buffer_t *buf; int size; -#if !PPP_SYNC uint8_t *src, *dst, byte; uint16_t fcs; int n; -#endif switch (conn->sstp_state) { case STATE_SERVER_CALL_CONNECTED_PENDING: @@ -1809,16 +1972,21 @@ static int sstp_recv_data_packet(struct sstp_conn_t *conn, struct sstp_hdr *hdr) if (size == 0) return 0; -#if PPP_SYNC - buf = alloc_buf(size); - if (!buf) { - log_error("sstp: no memory\n"); - return -1; + if (conn->ppp_mode == PPP_MODE_SEQPACKET) { + /* one datagram is one frame, no framing needed */ + buf = alloc_buf(size); + if (!buf) { + log_error("sstp: no memory\n"); + return -1; + } + + buf_put_data(buf, hdr->data, size); + + return ppp_send(conn, buf); } - buf_put_data(buf, hdr->data, size); -#else - buf = alloc_buf(size*2 + 2 + PPP_FCSLEN); + /* payload and FCS octets may both double when escaped, plus 2 flags */ + buf = alloc_buf((size + PPP_FCSLEN) * 2 + 2); if (!buf) { log_error("sstp: no memory\n"); return -1; @@ -1847,7 +2015,6 @@ static int sstp_recv_data_packet(struct sstp_conn_t *conn, struct sstp_hdr *hdr) *dst++ = PPP_FLAG; buf_put(buf, dst - buf->tail); -#endif return ppp_send(conn, buf); } @@ -1911,8 +2078,8 @@ static int sstp_handler(struct sstp_conn_t *conn, struct buffer_t *buf) } n = ntohs(hdr->length); - if (n > SSTP_MAX_PACKET_SIZE) { - log_ppp_error("recv [SSTP too long packet]\n"); + if (n < sizeof(*hdr) || n > SSTP_MAX_PACKET_SIZE) { + log_ppp_error("recv [SSTP invalid packet length %d]\n", n); return -1; } else if (n > buf->len) break; @@ -2064,8 +2231,9 @@ static int sstp_write(struct triton_md_handler_t *h) continue; if (errno == EAGAIN) goto defer; - if (conf_verbose && errno != EPIPE) - log_ppp_info2("sstp: write: %s\n", strerror(errno)); + if (errno == EPIPE) + goto drop; + log_ppp_error("sstp: write: %s\n", strerror(errno)); goto drop; } else if (n == 0) goto defer; @@ -2099,6 +2267,31 @@ static int sstp_send(struct sstp_conn_t *conn, struct buffer_t *buf) return 0; } +static void sstp_flush(struct sstp_conn_t *conn) +{ + struct buffer_t *buf; + int n; + + while (!list_empty(&conn->out_queue)) { + buf = list_first_entry(&conn->out_queue, typeof(*buf), entry); + while (buf->len) { + n = conn->stream->write(conn->stream, buf->head, buf->len); + if (n < 0) { + if (errno == EINTR) + continue; + if (errno == EPIPE) + break; + log_ppp_error("sstp: write: %s\n", strerror(errno)); + break; + } else if (n == 0) + break; + buf_pull(buf, n); + } + list_del(&buf->entry); + free_buf(buf); + } +} + static void sstp_msg_echo(struct triton_timer_t *t) { struct sstp_conn_t *conn = container_of(t, typeof(*conn), hello_timer); @@ -2200,6 +2393,7 @@ static void sstp_disconnect(struct sstp_conn_t *conn) triton_timer_del(&conn->hello_timer); if (conn->hnd.tpd) { + sstp_flush(conn); triton_md_unregister_handler(&conn->hnd, 0); conn->stream->close(conn->stream); } @@ -2208,17 +2402,17 @@ static void sstp_disconnect(struct sstp_conn_t *conn) switch (conn->ppp_state) { case STATE_INIT: - __sync_sub_and_fetch(&stat_starting, 1); + sstp_stat_dec(&serv.stat.starting); break; case STATE_STARTING: case STATE_AUTHORIZED: case STATE_STARTED: conn->ppp_state = STATE_FINISHED; - __sync_sub_and_fetch(&stat_active, 1); + sstp_stat_dec(&serv.stat.active); ap_session_terminate(&conn->ppp.ses, TERM_LOST_CARRIER, 1); break; case STATE_FINISHED: - __sync_sub_and_fetch(&stat_active, 1); + sstp_stat_dec(&serv.stat.active); break; } triton_event_fire(EV_CTRL_FINISHED, &conn->ppp.ses); @@ -2254,11 +2448,9 @@ static void sstp_start(struct sstp_conn_t *conn) { log_debug("sstp: starting\n"); -#ifdef CRYPTO_OPENSSL if (serv.ssl_ctx) conn->stream = ssl_stream_init(conn->hnd.fd, serv.ssl_ctx); else -#endif conn->stream = stream_init(conn->hnd.fd); if (!conn->stream) { log_error("sstp: stream open error: %s\n", strerror(errno)); @@ -2299,12 +2491,12 @@ static int sstp_connect(struct triton_md_handler_t *h) continue; } - if (conf_max_starting && ap_session_stat.starting >= conf_max_starting) { + if (conf_max_starting && ap_session_stat_starting() >= conf_max_starting) { close(sock); continue; } - if (conf_max_sessions && ap_session_stat.active + ap_session_stat.starting >= conf_max_sessions) { + if (conf_max_sessions && ap_session_stat_active() + ap_session_stat_starting() >= conf_max_sessions) { close(sock); continue; } @@ -2371,6 +2563,7 @@ static int sstp_connect(struct triton_md_handler_t *h) conn->sstp_state = STATE_SERVER_CALL_DISCONNECTED; conn->ppp_state = STATE_INIT; + conn->ppp_mode = conf_ppp_mode; conn->handler = conf_proxyproto ? proxy_handler : http_handler; //conn->bypass_auth = conf_bypass_auth; @@ -2425,7 +2618,7 @@ static int sstp_connect(struct triton_md_handler_t *h) triton_event_fire(EV_CTRL_STARTING, &conn->ppp.ses); - __sync_add_and_fetch(&stat_starting, 1); + sstp_stat_inc(&serv.stat.starting); } return 0; @@ -2438,17 +2631,14 @@ static void sstp_serv_close(struct triton_context_t *ctx) triton_md_unregister_handler(&serv->hnd, 1); triton_context_unregister(ctx); -#ifdef CRYPTO_OPENSSL if (serv->ssl_ctx) SSL_CTX_free(serv->ssl_ctx); serv->ssl_ctx = NULL; -#endif if (serv->addr.u.sa.sa_family == AF_UNIX && serv->addr.u.sun.sun_path[0]) unlink(serv->addr.u.sun.sun_path); } -#ifdef CRYPTO_OPENSSL #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME static int ssl_servername(SSL *ssl, int *al, void *arg) { @@ -2482,6 +2672,13 @@ static void ssl_info_cb(const SSL *ssl, int where, int ret) #endif #endif +static void ssl_set_cert_hashes(const X509 *cert) { + if (conf_hash_protocol & CERT_HASH_PROTOCOL_SHA1) + X509_digest(cert, EVP_sha1(), conf_hash_sha1.hash, &conf_hash_sha1.len); + if (conf_hash_protocol & CERT_HASH_PROTOCOL_SHA256) + X509_digest(cert, EVP_sha256(), conf_hash_sha256.hash, &conf_hash_sha256.len); +} + static void ssl_load_config(struct sstp_serv_t *serv, const char *servername) { SSL_CTX *old_ctx, *ssl_ctx = NULL; @@ -2489,26 +2686,6 @@ static void ssl_load_config(struct sstp_serv_t *serv, const char *servername) BIO *in = NULL; char *opt; - opt = conf_get_opt("sstp", "ssl-pemfile"); - if (opt) { - in = BIO_new(BIO_s_file()); - if (!in) { - log_error("sstp: %s error: %s\n", "ssl-pemfile", ERR_error_string(ERR_get_error(), NULL)); - goto error; - } - - if (BIO_read_filename(in, opt) <= 0) { - log_error("sstp: %s error: %s\n", "ssl-pemfile", ERR_error_string(ERR_get_error(), NULL)); - goto error; - } - - cert = PEM_read_bio_X509(in, NULL, NULL, NULL); - if (!cert) { - log_error("sstp: %s error: %s\n", "ssl-pemfile", ERR_error_string(ERR_get_error(), NULL)); - goto error; - } - } - opt = conf_get_opt("sstp", "accept"); if (opt && strhas(opt, "ssl", ',')) { legacy_ssl: @@ -2601,6 +2778,8 @@ static void ssl_load_config(struct sstp_serv_t *serv, const char *servername) #else DH *dh; + in = BIO_new(BIO_s_file()); + if (BIO_read_filename(in, opt) <= 0) { log_error("sstp: %s error: %s\n", "ssl-dhparam", ERR_error_string(ERR_get_error(), NULL)); goto error; @@ -2612,6 +2791,10 @@ static void ssl_load_config(struct sstp_serv_t *serv, const char *servername) goto error; } + if (!BIO_free(in)) + abort(); + in = NULL; + SSL_CTX_set_tmp_dh(ssl_ctx, dh); DH_free(dh); #endif @@ -2664,12 +2847,21 @@ static void ssl_load_config(struct sstp_serv_t *serv, const char *servername) if (opt && atoi(opt)) SSL_CTX_set_options(ssl_ctx, SSL_OP_CIPHER_SERVER_PREFERENCE); - if (cert && SSL_CTX_use_certificate(ssl_ctx, cert) != 1) { - log_error("sstp: %s error: %s\n", "ssl-pemfile", ERR_error_string(ERR_get_error(), NULL)); - goto error; + opt = conf_get_opt("sstp", "ssl-pemfile"); + if (opt) { + if (SSL_CTX_use_certificate_chain_file(ssl_ctx, opt) != 1) { + log_error("sstp: %s error: %s\n", "ssl-pemfile", ERR_error_string(ERR_get_error(), NULL)); + goto error; + } + // cert is a reference. Do not free it. + X509 *cert_ref = SSL_CTX_get0_certificate(ssl_ctx); + if (!cert_ref) { + log_error("sstp: %s error: %s\n", "ssl-pemfile", ERR_error_string(ERR_get_error(), NULL)); + goto error; + } + ssl_set_cert_hashes(cert_ref); } - - opt = conf_get_opt("sstp", "ssl-keyfile") ? : conf_get_opt("sstp", "ssl-pemfile"); + opt = conf_get_opt("sstp", "ssl-keyfile") ? : opt; if ((opt && SSL_CTX_use_PrivateKey_file(ssl_ctx, opt, SSL_FILETYPE_PEM) != 1) || SSL_CTX_check_private_key(ssl_ctx) != 1) { log_error("sstp: %s error: %s\n", "ssl-keyfile", ERR_error_string(ERR_get_error(), NULL)); @@ -2697,13 +2889,30 @@ static void ssl_load_config(struct sstp_serv_t *serv, const char *servername) opt = conf_get_opt("sstp", "ssl"); if (opt && atoi(opt) > 0) goto legacy_ssl; - } - if (cert) { - if (conf_hash_protocol & CERT_HASH_PROTOCOL_SHA1) - X509_digest(cert, EVP_sha1(), conf_hash_sha1.hash, &conf_hash_sha1.len); - if (conf_hash_protocol & CERT_HASH_PROTOCOL_SHA256) - X509_digest(cert, EVP_sha256(), conf_hash_sha256.hash, &conf_hash_sha256.len); + opt = conf_get_opt("sstp", "ssl-pemfile"); + if (opt) { + in = BIO_new(BIO_s_file()); + if (!in) { + log_error("sstp: %s error: %s\n", "ssl-pemfile", ERR_error_string(ERR_get_error(), NULL)); + goto error; + } + + if (BIO_read_filename(in, opt) <= 0) { + log_error("sstp: %s error: %s\n", "ssl-pemfile", ERR_error_string(ERR_get_error(), NULL)); + goto error; + } + + cert = PEM_read_bio_X509(in, NULL, NULL, NULL); + if (!cert) { + log_error("sstp: %s error: %s\n", "ssl-pemfile", ERR_error_string(ERR_get_error(), NULL)); + goto error; + } + if (!BIO_free(in)) + abort(); + in = NULL; + ssl_set_cert_hashes(cert); + } } old_ctx = serv->ssl_ctx; @@ -2715,10 +2924,9 @@ error: SSL_CTX_free(ssl_ctx); if (cert) X509_free(cert); - if (in) - BIO_free(in); + if (in && !BIO_free(in)) + abort(); } -#endif static void ev_mppe_keys(struct ev_mppe_keys_t *ev) { @@ -2752,19 +2960,17 @@ static void ev_ses_authorized(struct ap_session *ses) static int show_stat_exec(const char *cmd, char * const *fields, int fields_cnt, void *client) { + struct sstp_stat_t stat; + + sstp_stat_get(&stat); + cli_send(client, "sstp:\r\n"); - cli_sendv(client," starting: %u\r\n", stat_starting); - cli_sendv(client," active: %u\r\n", stat_active); + cli_sendv(client," starting: %u\r\n", stat.starting); + cli_sendv(client," active: %u\r\n", stat.active); return CLI_CMD_OK; } -void __export sstp_get_stat(unsigned int **starting, unsigned int **active) -{ - *starting = &stat_starting; - *active = &stat_active; -} - static void load_config(void) { int ipmode; @@ -2779,15 +2985,15 @@ static void load_config(void) opt = conf_get_opt("sstp", "http-error"); if (opt) { if (strcmp(opt, "deny") == 0) - conf_http_mode = 0; + conf_http_mode = HTTP_ERR_DENY; else if (strcmp(opt, "allow") == 0) - conf_http_mode = -1; + conf_http_mode = HTTP_ERR_ALLOW; else if (strstr(opt, "://") != NULL) { conf_http_url = opt; opt = strstr(opt, "://") + 3; while (*opt == '/') opt++; - conf_http_mode = strchr(opt, '/') ? 1 : 2; + conf_http_mode = strchr(opt, '/') ? HTTP_ERR_REDIRECT : HTTP_ERR_REDIRECT_APPEND; } } @@ -2803,15 +3009,15 @@ static void load_config(void) opt = conf_get_opt("sstp", "accept"); conf_proxyproto = opt && strhas(opt, "proxy", ','); -#ifdef CRYPTO_OPENSSL ssl_load_config(&serv, conf_hostname); - opt = serv.ssl_ctx ? "enabled" : "disabled"; -#else - opt = "not available"; -#endif + if (conf_verbose) { - log_info2("sstp: SSL/TLS support %s, PROXY support %s\n", - opt, conf_proxyproto ? "enabled" : "disabled"); + log_info2("sstp: SSL/TLS %s, PROXY %s, PPP mode %s\n", + serv.ssl_ctx ? "enabled" : "disabled", + conf_proxyproto ? "enabled" : "disabled", + conf_ppp_mode == PPP_MODE_AUTO ? "AUTO" : + conf_ppp_mode == PPP_MODE_ASYNC ? "ASYNC" : + conf_ppp_mode == PPP_MODE_SEQPACKET ? "SEQPACKET" : "unknown"); } opt = conf_get_opt("sstp", "cert-hash-sha1"); @@ -2885,7 +3091,7 @@ static void sstp_init(void) struct sockaddr_t *addr = &serv.addr; struct linger linger; struct stat st; - int port, value; + int port, value, fd; char *opt; opt = conf_get_opt("sstp", "port"); @@ -2963,6 +3169,32 @@ static void sstp_init(void) goto error_unlink; } + opt = conf_get_opt("sstp", "ppp-mode"); + if (opt) { + if (!strcmp(opt, "auto")) + conf_ppp_mode = PPP_MODE_AUTO; + else if (!strcmp(opt, "seqpacket")) + conf_ppp_mode = PPP_MODE_SEQPACKET; + else if (!strcmp(opt, "async")) + conf_ppp_mode = PPP_MODE_ASYNC; + } + if (conf_ppp_mode != PPP_MODE_ASYNC) { + fd = socket(AF_PPPOX, SOCK_SEQPACKET, PX_PROTO_OSEQ); + if (fd >= 0) + close(fd); + else if (access("/sys/module/ppposeq", F_OK) && system("modprobe -q ppposeq")) + log_warn("failed to load ppposeq kernel module\n"); + } + if (conf_ppp_mode == PPP_MODE_AUTO) { + fd = socket(AF_PPPOX, SOCK_SEQPACKET, PX_PROTO_OSEQ); + if (fd >= 0) { + conf_ppp_mode = PPP_MODE_SEQPACKET; + close(fd); + } else { + conf_ppp_mode = PPP_MODE_ASYNC; + } + } + conn_pool = mempool_create(sizeof(struct sstp_conn_t)); load_config(); diff --git a/accel-pppd/ctrl/sstp/sstp.h b/accel-pppd/ctrl/sstp/sstp.h new file mode 100644 index 00000000..eff8e053 --- /dev/null +++ b/accel-pppd/ctrl/sstp/sstp.h @@ -0,0 +1,14 @@ +#ifndef __SSTP_H +#define __SSTP_H + +struct sstp_stat_t +{ + unsigned int starting; + unsigned int active; +}; + +void sstp_stat_get(struct sstp_stat_t *stat); +unsigned int sstp_stat_starting(void); +unsigned int sstp_stat_active(void); + +#endif diff --git a/accel-pppd/extra/CMakeLists.txt b/accel-pppd/extra/CMakeLists.txt index 6badcd5e..08466f24 100644 --- a/accel-pppd/extra/CMakeLists.txt +++ b/accel-pppd/extra/CMakeLists.txt @@ -5,11 +5,13 @@ set_property(TARGET pppd_compat PROPERTY INSTALL_RPATH ${CMAKE_INSTALL_PREFIX}/l ADD_LIBRARY(ippool SHARED ippool.c) ADD_LIBRARY(ipv6pool SHARED ipv6pool.c) ADD_LIBRARY(chap-secrets SHARED chap-secrets.c) +TARGET_LINK_LIBRARIES(chap-secrets ${crypto_lib}) ADD_LIBRARY(logwtmp SHARED logwtmp.c) TARGET_LINK_LIBRARIES(logwtmp util) ADD_LIBRARY(connlimit SHARED connlimit.c) +ADD_LIBRARY(metrics SHARED metrics.c) -INSTALL(TARGETS pppd_compat ippool ipv6pool sigchld chap-secrets logwtmp connlimit +INSTALL(TARGETS pppd_compat ippool ipv6pool sigchld chap-secrets logwtmp connlimit metrics LIBRARY DESTINATION lib${LIB_SUFFIX}/accel-ppp ) diff --git a/accel-pppd/extra/bitpool.h b/accel-pppd/extra/bitpool.h new file mode 100644 index 00000000..fa739a28 --- /dev/null +++ b/accel-pppd/extra/bitpool.h @@ -0,0 +1,88 @@ +#ifndef __BITPOOL_H +#define __BITPOOL_H + +#include <stdint.h> +#include <stddef.h> + +/* + * Fixed-size bit array shared by the IPv4/IPv6 address pool allocators. + * + * Semantics: bit == 1 => allocated/unavailable, bit == 0 => free. A freshly + * calloc'd map is therefore all-free. The caller is responsible for pre-setting + * to 1 any bit it never wants handed out: + * - "remainder" bits in the final word (indices >= count), and + * - reserved in-range bits (e.g. the p2p gateway collision), + * so the word-at-a-time scan can never return a non-existent unit. + */ + +typedef unsigned long bm_word_t; +#define BM_WORD_BITS (8 * sizeof(bm_word_t)) +#define BM_NWORDS(n) (((n) + BM_WORD_BITS - 1) / BM_WORD_BITS) +#define BM_INVALID ((uint64_t)-1) + +static inline void bm_set(bm_word_t *bm, uint64_t i) +{ + bm[i / BM_WORD_BITS] |= (bm_word_t)1 << (i % BM_WORD_BITS); +} + +static inline void bm_clear(bm_word_t *bm, uint64_t i) +{ + bm[i / BM_WORD_BITS] &= ~((bm_word_t)1 << (i % BM_WORD_BITS)); +} + +static inline int bm_test(const bm_word_t *bm, uint64_t i) +{ + return (bm[i / BM_WORD_BITS] >> (i % BM_WORD_BITS)) & 1; +} + +/* + * Find the first free (0) bit at or after `from`, wrapping once back to 0. + * `count` is the number of valid bits; remainder bits in the final word must + * already be set to 1 by the caller so they are never returned. Returns the + * bit index in [0,count), or BM_INVALID if every bit is used. + * + * Word-at-a-time: a fully-used word (~w == 0) is skipped in one branch, so the + * scan is O(1) amortized for a sparse pool and O(count/word) worst case. + */ +static inline uint64_t bm_find_free(const bm_word_t *bm, uint64_t count, uint64_t from) +{ + uint64_t nwords, fw, i; + unsigned fb; + + if (!count) + return BM_INVALID; + if (from >= count) + from = 0; + + nwords = BM_NWORDS(count); + fw = from / BM_WORD_BITS; + fb = from % BM_WORD_BITS; + + /* Probe nwords+1 times: the start word is examined first with its low bits + * (< from) masked off, and once more at the end with only those low bits, + * so every bit is considered exactly once across the wrap. */ + for (i = 0; i <= nwords; i++) { + uint64_t word = fw + i; + bm_word_t inv; + + if (word >= nwords) + word -= nwords; + + inv = ~bm[word]; + + if (i == 0) + inv &= ~(((bm_word_t)1 << fb) - 1); /* skip bits below `from` */ + else if (i == nwords) + inv &= ((bm_word_t)1 << fb) - 1; /* wrapped: only those skipped bits */ + + if (inv) { + uint64_t bit = word * BM_WORD_BITS + __builtin_ctzl(inv); + if (bit < count) + return bit; + } + } + + return BM_INVALID; +} + +#endif diff --git a/accel-pppd/extra/bitpool_test.c b/accel-pppd/extra/bitpool_test.c new file mode 100644 index 00000000..347f1770 --- /dev/null +++ b/accel-pppd/extra/bitpool_test.c @@ -0,0 +1,256 @@ +/* + * Standalone sanity test for the bitmap IP-pool allocator core. + * + * Not part of the cmake build. Compile and run with: + * gcc -O2 -Wall -o /tmp/bitpool_test accel-pppd/extra/bitpool_test.c && /tmp/bitpool_test + * + * It validates: + * - bitpool.h: bm_set/clear/test, bm_find_free wrap + remainder bits + + * exhaustion, and that every freed bit is re-handed exactly once. + * - IPv4 addr<->bit round-trip for p2p and net30 geometry, incl. alignment + * and out-of-range rejection. + * - IPv6 prefix<->bit round-trip (the exact helpers used by ipv6pool.c), + * cross-checked against __int128 reference math, incl. prefix_len==128, + * mask<=64 / mask>64, and an unaligned base. + */ +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <assert.h> + +#include "bitpool.h" + +static int failures; +#define CHECK(cond) do { if (!(cond)) { \ + fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); failures++; } } while (0) + +/* ---- mimic a finalized range bitmap: count bits free, remainder bits set ---- */ +static bm_word_t *make_map(uint64_t count) +{ + uint64_t nw = BM_NWORDS(count), b; + bm_word_t *bm = calloc(nw, sizeof(bm_word_t)); + for (b = count; b < nw * BM_WORD_BITS; b++) + bm_set(bm, b); + return bm; +} + +static void test_bitmap_basic(void) +{ + uint64_t count = 200; /* not a multiple of 64 -> exercises remainder */ + bm_word_t *bm = make_map(count); + uint64_t i, bit, n = 0; + char *seen = calloc(count, 1); + + /* allocate the whole pool via rolling cursor; each bit unique and < count */ + uint64_t cursor = 0; + while ((bit = bm_find_free(bm, count, cursor)) != BM_INVALID) { + CHECK(bit < count); + CHECK(!seen[bit]); + seen[bit] = 1; + bm_set(bm, bit); + cursor = bit + 1; + n++; + CHECK(n <= count); + } + CHECK(n == count); /* exactly count handed out */ + for (i = 0; i < count; i++) + CHECK(seen[i]); /* every unit handed out once */ + CHECK(bm_find_free(bm, count, 0) == BM_INVALID); /* exhausted */ + + /* free three, confirm exactly those three come back */ + bm_clear(bm, 5); bm_clear(bm, 130); bm_clear(bm, 199); + memset(seen, 0, count); + for (n = 0; (bit = bm_find_free(bm, count, 0)) != BM_INVALID; n++) { + CHECK(bit == 5 || bit == 130 || bit == 199); + seen[bit] = 1; bm_set(bm, bit); + } + CHECK(n == 3 && seen[5] && seen[130] && seen[199]); + + /* wrap-around: only bit 1 free, search starting past it must still find it */ + memset(bm, 0xff, BM_NWORDS(count) * sizeof(bm_word_t)); + bm_clear(bm, 1); + CHECK(bm_find_free(bm, count, 50) == 1); + CHECK(bm_find_free(bm, count, 0) == 1); + + /* zero-size pool */ + CHECK(bm_find_free(bm, 0, 0) == BM_INVALID); + + free(seen); + free(bm); + printf("ok bitmap basic/wrap/remainder/exhaustion\n"); +} + +/* ---- IPv4 geometry (mirrors ippool.c range_addr_to_bit / bit->addr) ---- */ +static uint64_t v4_addr_to_bit(uint32_t start, uint32_t step, int peer_off, + uint64_t count, uint32_t peer) +{ + uint32_t base, delta; + uint64_t bit; + if (peer < (uint32_t)peer_off) return BM_INVALID; + base = peer - peer_off; + if (base < start) return BM_INVALID; + delta = base - start; + if (delta % step) return BM_INVALID; + bit = delta / step; + if (bit >= count) return BM_INVALID; + return bit; +} + +static void test_v4(void) +{ + /* p2p: 10.0.0.0 - 10.0.1.255 (512 addrs), step 1, peer_off 0 */ + uint32_t start = (10u << 24); + uint32_t end = (10u << 24) | 0x1ff; + uint64_t count = (uint64_t)(end - start) + 1; + uint64_t i; + CHECK(count == 512); + for (i = 0; i < count; i++) { + uint32_t peer = start + (uint32_t)i * 1 + 0; + CHECK(v4_addr_to_bit(start, 1, 0, count, peer) == i); + } + CHECK(v4_addr_to_bit(start, 1, 0, count, start - 1) == BM_INVALID); + CHECK(v4_addr_to_bit(start, 1, 0, count, end + 1) == BM_INVALID); + printf("ok v4 p2p round-trip (512 addrs)\n"); + + /* net30: same range, step 4, gw_off 1, peer_off 2 -> 128 blocks */ + count = ((uint64_t)(end - start) + 1) / 4; + CHECK(count == 128); + for (i = 0; i < count; i++) { + uint32_t base = start + (uint32_t)i * 4; + uint32_t peer = base + 2; /* .2 client */ + CHECK(v4_addr_to_bit(start, 4, 2, count, peer) == i); + /* .1 router and .0/.3 are NOT valid leases */ + CHECK(v4_addr_to_bit(start, 4, 2, count, base + 1) == BM_INVALID); + CHECK(v4_addr_to_bit(start, 4, 2, count, base + 0) == BM_INVALID); + CHECK(v4_addr_to_bit(start, 4, 2, count, base + 3) == BM_INVALID); + } + printf("ok v4 net30 round-trip + alignment (128 blocks)\n"); +} + +/* ---- IPv6 helpers: EXACT copies of the ones in ipv6pool.c ---- */ +static void in6_sub(uint8_t *o, const uint8_t *a, const uint8_t *b) +{ + int i, borrow = 0; + for (i = 15; i >= 0; i--) { + int v = (int)a[i] - b[i] - borrow; + if (v < 0) { v += 256; borrow = 1; } else borrow = 0; + o[i] = v; + } +} +static int in6_shr_u64(const uint8_t *d, int shift, uint64_t *out) +{ + int p; uint64_t v = 0; + for (p = 0; p < 64; p++) { + int sp = shift + p; + if (sp < 128 && (d[15 - (sp >> 3)] & (1 << (sp & 7)))) v |= (uint64_t)1 << p; + } + for (p = shift + 64; p < 128; p++) + if (d[15 - (p >> 3)] & (1 << (p & 7))) return -1; + *out = v; return 0; +} +static void in6_index_to_addr(uint8_t *out, const uint8_t *start, uint64_t i, int shift) +{ + uint8_t add[16] = { 0 }; int p, b, carry = 0; + for (p = 0; p < 64; p++) + if (i & ((uint64_t)1 << p)) { int sp = shift + p; if (sp < 128) add[15 - (sp >> 3)] |= (1 << (sp & 7)); } + memcpy(out, start, 16); + for (b = 15; b >= 0; b--) { int s = out[b] + add[b] + carry; out[b] = s & 0xff; carry = s >> 8; } +} +static int in6_cmp(const uint8_t *a, const uint8_t *b) +{ + int i; for (i = 0; i < 16; i++) { if (a[i] < b[i]) return -1; if (a[i] > b[i]) return 1; } return 0; +} +static uint64_t v6_addr_to_bit(const uint8_t *start, int shift, uint64_t count, const uint8_t *addr) +{ + uint8_t delta[16]; uint64_t i; int p; + if (in6_cmp(addr, start) < 0) return BM_INVALID; + in6_sub(delta, addr, start); + for (p = 0; p < shift; p++) + if (delta[15 - (p >> 3)] & (1 << (p & 7))) return BM_INVALID; + if (in6_shr_u64(delta, shift, &i)) return BM_INVALID; + if (i >= count) return BM_INVALID; + return i; +} + +/* ---- __int128 reference ---- */ +static __uint128_t to_u128(const uint8_t a[16]) { __uint128_t v = 0; int i; for (i = 0; i < 16; i++) v = (v << 8) | a[i]; return v; } +static void from_u128(uint8_t a[16], __uint128_t v) { int i; for (i = 15; i >= 0; i--) { a[i] = v & 0xff; v >>= 8; } } + +static void test_v6_one(const char *label, const uint8_t start[16], int mask, int prefix_len) +{ + int shift = 128 - prefix_len; + __uint128_t s = to_u128(start); + __uint128_t hostmask = (mask == 0) ? ~(__uint128_t)0 : (((__uint128_t)1 << (128 - mask)) - 1); + __uint128_t end = s | hostmask; + uint8_t endb[16]; uint8_t delta[16]; uint64_t span, count, i, step_lo; + __uint128_t step = (__uint128_t)1 << shift; + + from_u128(endb, end); + in6_sub(delta, endb, start); + CHECK(in6_shr_u64(delta, shift, &span) == 0); + count = span + 1; + CHECK(count == (uint64_t)(((end - s) >> shift) + 1)); /* matches reference */ + + /* round-trip boundary + sampled indices */ + uint64_t samples[] = { 0, 1, count / 2, count - 1 }; + for (size_t k = 0; k < sizeof(samples) / sizeof(samples[0]); k++) { + i = samples[k]; + if (i >= count) continue; + uint8_t addr[16]; in6_index_to_addr(addr, start, i, shift); + __uint128_t ref = s + (__uint128_t)i * step; + uint8_t refb[16]; from_u128(refb, ref); + CHECK(memcmp(addr, refb, 16) == 0); /* bit->addr matches ref */ + CHECK(v6_addr_to_bit(start, shift, count, addr) == i); /* addr->bit round-trip */ + /* misaligned address (only when there is room below a prefix) */ + if (shift > 0) { + uint8_t bad[16]; memcpy(bad, addr, 16); bad[15] |= 1; + CHECK(v6_addr_to_bit(start, shift, count, bad) == BM_INVALID); + } + } + /* below start and past end reject */ + if (s > 0) { uint8_t below[16]; from_u128(below, s - 1); CHECK(v6_addr_to_bit(start, shift, count, below) == BM_INVALID); } + { uint8_t past[16]; from_u128(past, s + (__uint128_t)count * step); CHECK(v6_addr_to_bit(start, shift, count, past) == BM_INVALID); } + + (void)step_lo; + printf("ok v6 %s (mask=%d plen=%d shift=%d count=%llu)\n", + label, mask, prefix_len, shift, (unsigned long long)count); +} + +static void test_v6(void) +{ + uint8_t a[16]; + + /* 2001:db8::/48 carved into /64s -> 65536 prefixes (mask<=64) */ + memset(a, 0, 16); a[0] = 0x20; a[1] = 0x01; a[2] = 0x0d; a[3] = 0xb8; + test_v6_one("/48->/64", a, 48, 64); + + /* mask>64: 2001:db8:0:0:8000::/72 -> /80 (mask>64 path) */ + memset(a, 0, 16); a[0] = 0x20; a[1] = 0x01; a[2] = 0x0d; a[3] = 0xb8; a[8] = 0x80; + test_v6_one("/72->/80", a, 72, 80); + + /* prefix_len == 128: single addresses, step 1 */ + memset(a, 0, 16); a[0] = 0xfc; a[15] = 0x00; + test_v6_one("/120->/128", a, 120, 128); + + /* unaligned base within the mask host bits (start not on a /64 boundary) */ + memset(a, 0, 16); a[0] = 0x20; a[1] = 0x01; a[2] = 0x0d; a[3] = 0xb8; a[7] = 0x05; + test_v6_one("/48->/64 unaligned base", a, 48, 64); + + /* big delegation: /32 -> /56 = 2^24 prefixes (the cap boundary) */ + memset(a, 0, 16); a[0] = 0x2a; a[1] = 0x00; + test_v6_one("/32->/56 (cap)", a, 32, 56); +} + +int main(void) +{ + test_bitmap_basic(); + test_v4(); + test_v6(); + if (failures) { + printf("\n%d CHECK(s) FAILED\n", failures); + return 1; + } + printf("\nALL TESTS PASSED\n"); + return 0; +} diff --git a/accel-pppd/extra/chap-secrets.c b/accel-pppd/extra/chap-secrets.c index b486bb11..5fd0102c 100644 --- a/accel-pppd/extra/chap-secrets.c +++ b/accel-pppd/extra/chap-secrets.c @@ -6,9 +6,10 @@ #include <netinet/in.h> #include <arpa/inet.h> -#ifdef CRYPTO_OPENSSL -#include "crypto.h" -#endif +#include <openssl/md4.h> +#include <openssl/sha.h> +#include <openssl/des.h> +#include <openssl/evp.h> #include "pwdb.h" #include "ipdb.h" @@ -29,13 +30,11 @@ static int conf_netmask = 0; static void *pd_key; static struct ipdb_t ipdb; -#ifdef CRYPTO_OPENSSL struct hash_chain { struct list_head entry; const EVP_MD *md; }; -#endif struct cs_pd_t { @@ -46,9 +45,7 @@ struct cs_pd_t char *pool; }; -#ifdef CRYPTO_OPENSSL static LIST_HEAD(hash_chain); -#endif static char *skip_word(char *ptr) { @@ -126,19 +123,16 @@ static struct cs_pd_t *create_pd(struct ap_session *ses, const char *username) int n; struct cs_pd_t *pd; struct in_addr in; -#ifdef CRYPTO_OPENSSL char username_hash[EVP_MAX_MD_SIZE * 2 + 1]; uint8_t hash[EVP_MAX_MD_SIZE]; struct hash_chain *hc; EVP_MD_CTX *md_ctx = NULL; char c; int i; -#endif if (!conf_chap_secrets) return NULL; -#ifdef CRYPTO_OPENSSL if (conf_encrypted && !list_empty(&hash_chain)) { unsigned int size = 0; list_for_each_entry(hc, &hash_chain, entry) { @@ -156,7 +150,6 @@ static struct cs_pd_t *create_pd(struct ap_session *ses, const char *username) username = username_hash; } -#endif f = fopen(conf_chap_secrets, "r"); if (!f) { @@ -192,10 +185,8 @@ out: return NULL; found: -#ifdef CRYPTO_OPENSSL if (conf_encrypted && strlen(ptr[1]) != 32) goto out; -#endif pd = _malloc(sizeof(*pd)); if (!pd) { @@ -205,7 +196,6 @@ found: memset(pd, 0, sizeof(*pd)); pd->pd.key = &pd_key; -#ifdef CRYPTO_OPENSSL if (conf_encrypted) { pd->passwd = _malloc(16); if (!pd->passwd) { @@ -221,7 +211,6 @@ found: ptr[1][i*2 + 2] = c; } } else -#endif { pd->passwd = _strdup(ptr[1]); if (!pd->passwd) { @@ -335,10 +324,8 @@ static char* get_passwd(struct pwdb_t *pwdb, struct ap_session *ses, const char { struct cs_pd_t *pd = find_pd(ses); -#ifdef CRYPTO_OPENSSL if (conf_encrypted) return NULL; -#endif if (!pd) pd = create_pd(ses, username); @@ -349,7 +336,6 @@ static char* get_passwd(struct pwdb_t *pwdb, struct ap_session *ses, const char return _strdup(pd->passwd); } -#ifdef CRYPTO_OPENSSL static void des_encrypt(const uint8_t *input, const uint8_t *key, uint8_t *output) { int i, j, parity; @@ -684,7 +670,6 @@ static int check_passwd(struct pwdb_t *pwdb, struct ap_session *ses, pwdb_callba return r; } -#endif static struct ipdb_t ipdb = { .get_ipv4 = get_ip, @@ -692,12 +677,9 @@ static struct ipdb_t ipdb = { static struct pwdb_t pwdb = { .get_passwd = get_passwd, -#ifdef CRYPTO_OPENSSL .check = check_passwd, -#endif }; -#ifdef CRYPTO_OPENSSL static void clear_hash_chain(void) { struct hash_chain *hc; @@ -731,7 +713,6 @@ static void parse_hash_chain(const char *opt) ptr1 = ptr2 + 1; } } -#endif static void parse_gw_ip_address(const char *opt) { @@ -792,12 +773,10 @@ static void load_config(void) else conf_encrypted = 0; -#ifdef CRYPTO_OPENSSL clear_hash_chain(); opt = conf_get_opt("chap-secrets", "username-hash"); if (opt) parse_hash_chain(opt); -#endif } static void init(void) diff --git a/accel-pppd/extra/ippool.c b/accel-pppd/extra/ippool.c index 3ae48e95..28aecd95 100644 --- a/accel-pppd/extra/ippool.c +++ b/accel-pppd/extra/ippool.c @@ -3,138 +3,161 @@ #include <stdint.h> #include <unistd.h> #include <string.h> +#include <pthread.h> #include <arpa/inet.h> +#include "triton.h" #include "events.h" #include "log.h" #include "list.h" #include "spinlock.h" #include "backup.h" +#include "ap_session.h" #include "ap_session_backup.h" #include "ipdb.h" +#include "cli.h" #ifdef RADIUS #include "radius.h" #endif +#include "bitpool.h" #include "memdebug.h" -struct ippool_t; - -typedef void (*generate_func)(struct ippool_t *); +/* + * Bitmap IPv4 address pool. + * + * Each pool holds a list of contiguous ranges; each range owns one bitmap + * (1 bit per allocatable block). A lease is a small per-session malloc wrapper + * around the ipv4db_item_t returned to the session, so pool memory is never + * shared/mutated by sessions. The whole pool set is rebuilt and swapped on + * EV_CONFIG_RELOAD, reconciling live sessions against the new ranges. + * + * TODO: a sparse/hierarchical allocator would lift IPPOOL_MAX_BITS; the dense + * bitmap is fine for realistic IPv4 ranges (a /8 sits right at the cap). + */ + +#define IPPOOL_MAX_BITS (1u << 24) /* ~16.7M units, ~2MB bitmap */ + +enum { + ORPHAN_KEEP = 0, /* keep the session, no-op its later put */ + ORPHAN_DISCONNECT, /* terminate sessions whose address left the pools */ +}; -struct ippool_t -{ +/* one contiguous range = one bitmap. step/offsets encode the allocator: + * p2p: step=1, gw_offset=-1 (local addr from gw-ip-address/0), peer_offset=0 + * net30: step=4, gw_offset=1 (.1 router), peer_offset=2 (.2 client) */ +struct ip_range { struct list_head entry; - char *name; - struct list_head gw_list; - struct list_head tunnel_list; - struct list_head items; - uint32_t startip; - uint32_t endip; - struct ippool_t *next; - generate_func generate; - spinlock_t lock; + uint32_t start; /* host order, first block base */ + uint32_t end; /* host order, last address in range */ + uint32_t step; + int gw_offset; + int peer_offset; + uint64_t count; /* number of blocks = bits in bitmap */ + uint64_t cursor; /* round-robin search hint */ + uint64_t used; + bm_word_t *bitmap; }; -struct ippool_item_t -{ - struct list_head entry; - struct ippool_t *pool; - struct ipv4db_item_t it; +struct ip_pool { + struct list_head entry; /* in set->pools; named pools only */ + char *name; /* NULL for the default pool */ + struct ip_pool *next; /* overflow chain */ + struct list_head ranges; + spinlock_t lock; /* guards every range's bitmap/cursor/used */ }; -struct ipaddr_t -{ +struct pool_set { + struct list_head pools; /* named pools */ + struct ip_pool *def_pool; /* unnamed default (not on `pools`) */ + in_addr_t gw_ip_address; /* prevents the configured gateway address from being handed out as a peer address */ + int shuffle; + int orphan_policy; +}; + +struct ip_lease { + struct ip_pool *pool; /* NULL once orphaned by a reload */ + struct ip_range *range; /* Range that supplied this lease's peer address; NULL if orphaned */ + struct ipv4db_item_t it; /* ses->ipv4 = &it */ +}; + +/* collected during reconcile, acted on after locks are dropped */ +struct disc_node { struct list_head entry; - in_addr_t addr; + struct ap_session *ses; }; static struct ipdb_t ipdb; -static in_addr_t conf_gw_ip_address; -static int conf_shuffle; +static pthread_rwlock_t pool_set_rwlock = PTHREAD_RWLOCK_INITIALIZER; +static struct pool_set *cur_set; #ifdef RADIUS static int conf_vendor = 0; static int conf_attr = 88; // Framed-Pool -#endif - -static int cnt; -static LIST_HEAD(pool_list); -static struct ippool_t *def_pool; -struct ippool_t *create_pool(char *name) +static int parse_attr_opt(const char *opt) { - struct ippool_t *p = malloc(sizeof(*p)); + struct rad_dict_attr_t *attr; + struct rad_dict_vendor_t *vendor; - memset(p, 0, sizeof(*p)); - p->name = name; + if (conf_vendor) + vendor = rad_dict_find_vendor_id(conf_vendor); + else + vendor = NULL; - INIT_LIST_HEAD(&p->gw_list); - INIT_LIST_HEAD(&p->tunnel_list); - INIT_LIST_HEAD(&p->items); - spinlock_init(&p->lock); + if (conf_vendor) { + if (vendor) + attr = rad_dict_find_vendor_attr(vendor, opt); + else + attr = NULL; + } else + attr = rad_dict_find_attr(opt); - if (name) - list_add_tail(&p->entry, &pool_list); + if (attr) + return attr->id; - return p; + return atoi(opt); } -struct ippool_t *find_pool(char *name, int create) +static int parse_vendor_opt(const char *opt) { - struct ippool_t *p; - - list_for_each_entry(p, &pool_list, entry) { - if (!strcmp(p->name, name)) - return p; - } + struct rad_dict_vendor_t *vendor; - if (create) - return create_pool(name); + vendor = rad_dict_find_vendor_name(opt); + if (vendor) + return vendor->id; - return NULL; + return atoi(opt); } +#endif -static void parse_gw_ip_address(const char *val) +/* ===== randomness for shuffle ===== */ + +static uint64_t rand_u64(void) { - char addr[17]; - char *ptr; + uint64_t r = 0; - if (!val) - return; + if (read(urandom_fd, &r, sizeof(r)) != sizeof(r)) + r = 0; - ptr = strchr(val, '/'); - if (ptr) { - // safeguard, don't crash on oversized or undersized strings - if (ptr - val > 15 || ptr - val < 7) - return; - memcpy(addr, val, ptr - val); - addr[ptr - val] = 0; - conf_gw_ip_address = inet_addr(addr); - } - else - conf_gw_ip_address = inet_addr(val); + return r; } +/* ===== range arithmetic ===== */ + //parses ranges like x.x.x.x/mask static int parse1(const char *str, uint32_t *begin, uint32_t *end) { int n; unsigned int f1, f2, f3, f4, m; - n = sscanf(str, "%u.%u.%u.%u/%u",&f1, &f2, &f3, &f4, &m); + n = sscanf(str, "%u.%u.%u.%u/%u", &f1, &f2, &f3, &f4, &m); if (n != 5) return -1; - if (f1 > 255) - return -1; - if (f2 > 255) - return -1; - if (f3 > 255) - return -1; - if (f4 > 255) + if (f1 > 255 || f2 > 255 || f3 > 255 || f4 > 255) return -1; if (m == 0 || m > 32) return -1; @@ -153,16 +176,10 @@ static int parse2(const char *str, uint32_t *begin, uint32_t *end) int n; unsigned int f1, f2, f3, f4, f5; - n = sscanf(str, "%u.%u.%u.%u-%u",&f1, &f2, &f3, &f4, &f5); + n = sscanf(str, "%u.%u.%u.%u-%u", &f1, &f2, &f3, &f4, &f5); if (n != 5) return -1; - if (f1 > 255) - return -1; - if (f2 > 255) - return -1; - if (f3 > 255) - return -1; - if (f4 > 255) + if (f1 > 255 || f2 > 255 || f3 > 255 || f4 > 255) return -1; if (f5 < f4 || f5 > 255) return -1; @@ -173,222 +190,514 @@ static int parse2(const char *str, uint32_t *begin, uint32_t *end) return 0; } -static void add_range(struct ippool_t *p, struct list_head *list, const char *name, void (*generate)(struct ippool_t *)) +/* bit index of a leased peer address within a range, or BM_INVALID */ +static uint64_t range_addr_to_bit(const struct ip_range *r, uint32_t peer_host) { - uint32_t i,startip, endip; - struct ipaddr_t *ip; + uint32_t base, delta; + uint64_t bit; + + if (peer_host < (uint32_t)r->peer_offset) + return BM_INVALID; + base = peer_host - r->peer_offset; + if (base < r->start) + return BM_INVALID; + delta = base - r->start; + if (delta % r->step) + return BM_INVALID; + bit = delta / r->step; + if (bit >= r->count) + return BM_INVALID; + + return bit; +} - if (parse1(name, &startip, &endip)) { - if (parse2(name, &startip, &endip)) { - fprintf(stderr, "ippool: cann't parse '%s'\n", name); - _exit(EXIT_FAILURE); - } +/* ===== pool set construction ===== */ + +static struct ip_pool *create_pool(struct pool_set *set, char *name) +{ + struct ip_pool *p = _malloc(sizeof(*p)); + + if (!p) + return NULL; + + memset(p, 0, sizeof(*p)); + p->name = name; + INIT_LIST_HEAD(&p->ranges); + spinlock_init(&p->lock); + + if (name) + list_add_tail(&p->entry, &set->pools); + + return p; +} + +static struct ip_pool *find_pool(struct pool_set *set, const char *name, int create) +{ + struct ip_pool *p; + + list_for_each_entry(p, &set->pools, entry) { + if (p->name && !strcmp(p->name, name)) + return p; } - for (i = startip; i <= endip; i++) { - ip = malloc(sizeof(*ip)); - ip->addr = htonl(i); - list_add_tail(&ip->entry, list); - cnt++; + if (create) { + char *dup = _strdup(name); + if (!dup) + return NULL; + return create_pool(set, dup); } - p->startip = startip; - p->endip = endip; - p->generate = generate; + return NULL; } -static uint8_t get_random() +static int add_range_to_pool(struct ip_pool *p, const char *str, + uint32_t step, int gw_offset, int peer_offset) { - static uint8_t buf[128]; - static int pos = 0; - int r; + uint32_t start, end; + uint64_t count; + struct ip_range *r; - if (pos == 0) - read(urandom_fd, buf, 128); + if (parse1(str, &start, &end)) { + if (parse2(str, &start, &end)) { + log_error("ippool: can't parse range '%s'\n", str); + return -1; + } + } - r = buf[pos++]; + if (end < start) { + log_error("ippool: range '%s' ends before it starts\n", str); + return -1; + } - if (pos == 128) - pos = 0; + count = ((uint64_t)(end - start) + 1) / step; /* floor: net30 tail dropped */ + if (count == 0) { + log_warn("ippool: range '%s' is empty for this allocator\n", str); + return 0; + } + if (count > IPPOOL_MAX_BITS) { + log_error("ippool: range '%s' has %llu units, exceeds cap %u; skipping\n", + str, (unsigned long long)count, IPPOOL_MAX_BITS); + return -1; + } - return r; + r = _malloc(sizeof(*r)); + if (!r) + return -1; + memset(r, 0, sizeof(*r)); + r->start = start; + r->end = end; + r->step = step; + r->gw_offset = gw_offset; + r->peer_offset = peer_offset; + r->count = count; + list_add_tail(&r->entry, &p->ranges); + + return 0; } -static void generate_pool_p2p(struct ippool_t *p) +/* allocate + finalize bitmaps once the whole section (incl. gw-ip-address) is known */ +static int finalize_ranges(struct pool_set *set, struct ip_pool *p) { - struct ippool_item_t *it; - struct ipaddr_t *addr = NULL; - struct ipaddr_t *peer_addr; - struct list_head *pos = NULL, *pos1 = p->tunnel_list.next, *pos2 = p->tunnel_list.prev; - uint8_t r, t = 0; - - while (1) { - if (list_empty(&p->tunnel_list)) - break; - else { - if (conf_shuffle) { - if (pos1 == &p->tunnel_list) - pos1 = pos1->next; - - if (pos2 == &p->tunnel_list) - pos2 = pos2->prev; - - if (t++ < 10) - r = get_random(); - else - r = get_random()%64; - - if (r < 32) - pos = pos1; - else if (r < 64) - pos = pos2; - - pos1 = pos1->next; - pos2 = pos2->prev; - - if (r >= 64) - continue; - - peer_addr = list_entry(pos, typeof(*peer_addr), entry); - if (pos == pos1) - pos1 = pos1->next; - - if (pos == pos2) - pos2 = pos2->prev; - - list_del(&peer_addr->entry); - t = 0; - } else { - peer_addr = list_entry(p->tunnel_list.next, typeof(*peer_addr), entry); - list_del(&peer_addr->entry); + struct ip_range *r; + + list_for_each_entry(r, &p->ranges, entry) { + uint64_t nw = BM_NWORDS(r->count); + uint64_t b; + + r->bitmap = _malloc(nw * sizeof(bm_word_t)); + if (!r->bitmap) + return -1; + memset(r->bitmap, 0, nw * sizeof(bm_word_t)); + + /* remainder bits past count must never be handed out */ + for (b = r->count; b < nw * BM_WORD_BITS; b++) + bm_set(r->bitmap, b); + + /* p2p: reserve the bit colliding with the configured gateway, + * reproducing the old generate_pool_p2p skip */ + if (r->step == 1 && r->peer_offset == 0 && set->gw_ip_address) { + uint32_t gw = ntohl(set->gw_ip_address); + if (gw >= r->start && gw <= r->end) { + uint64_t bit = gw - r->start; + if (bit < r->count && !bm_test(r->bitmap, bit)) { + bm_set(r->bitmap, bit); + r->used++; + } } } + } - if (!conf_gw_ip_address) { - if (list_empty(&p->gw_list)) - break; - else { - addr = list_entry(p->gw_list.next, typeof(*addr), entry); - list_del(&addr->entry); - } - } else if (conf_gw_ip_address == peer_addr->addr) - continue; + return 0; +} - it = malloc(sizeof(*it)); - if (!it) { - fprintf(stderr, "ippool: out of memory\n"); - break; +static void parse_gw_ip_address(const char *val, in_addr_t *out) +{ + char addr[17]; + char *ptr; + + if (!val) + return; + + ptr = strchr(val, '/'); + if (ptr) { + if (ptr - val > 15 || ptr - val < 7) + return; + memcpy(addr, val, ptr - val); + addr[ptr - val] = 0; + *out = inet_addr(addr); + } else + *out = inet_addr(val); +} + +/* parse ,name= / ,allocator= / ,next= from a raw option line */ +static int parse_line_opts(struct pool_set *set, const char *opt, struct ip_pool **pool, + uint32_t *step, int *gw_offset, int *peer_offset) +{ + char *name, *ptr; + + name = strstr(opt, ",name="); + if (name) { + name += sizeof(",name=") - 1; + ptr = strchrnul(name, ','); + name = _strndup(name, ptr - name); + if (!name) + return -1; + *pool = find_pool(set, name, 1); + _free(name); + } else if ((name = strchr(opt, ',')) && !strchr(name + 1, '=')) { + name = _strndup(name + 1, strchrnul(name + 1, ',') - (name + 1)); + if (!name) + return -1; + *pool = find_pool(set, name, 1); + _free(name); + } else + *pool = set->def_pool; + + if (!*pool) + return -1; + + /* defaults: p2p */ + *step = 1; + *gw_offset = -1; + *peer_offset = 0; + + name = strstr(opt, ",allocator="); + if (name) { + name += sizeof(",allocator=") - 1; + ptr = strchrnul(name, ','); + if (!strncmp(name, "p2p", ptr - name) && (size_t)(ptr - name) == 3) { + *step = 1; *gw_offset = -1; *peer_offset = 0; + } else if (!strncmp(name, "net30", ptr - name) && (size_t)(ptr - name) == 5) { + *step = 4; *gw_offset = 1; *peer_offset = 2; + } else { + log_error("ippool: '%s': unknown allocator\n", opt); + return -1; } + } - memset(it, 0, sizeof(*it)); - it->pool = p; - it->it.owner = &ipdb; - if (conf_gw_ip_address) - it->it.addr = conf_gw_ip_address; - else - it->it.addr = addr->addr; + name = strstr(opt, ",next="); + if (name) { + struct ip_pool *next; + name += sizeof(",next=") - 1; + ptr = strchrnul(name, ','); + name = _strndup(name, ptr - name); + if (!name) + return -1; + next = find_pool(set, name, 1); + _free(name); + if (next) + (*pool)->next = next; + } - it->it.peer_addr = peer_addr->addr; + return 0; +} - list_add_tail(&it->entry, &p->items); +static void free_pool(struct ip_pool *p) +{ + struct ip_range *r; + + while (!list_empty(&p->ranges)) { + r = list_first_entry(&p->ranges, typeof(*r), entry); + list_del(&r->entry); + if (r->bitmap) + _free(r->bitmap); + _free(r); } + if (p->name) + _free(p->name); + _free(p); } -static void generate_pool_net30(struct ippool_t *p) +static void free_pool_set(struct pool_set *set) { - struct ippool_item_t *it; - struct ipaddr_t *addr[4]; - int i; + struct ip_pool *p; - while (1) { - memset(addr, 0, sizeof(addr)); + if (!set) + return; - for (i = 0; i < 4; i++) { - if (list_empty(&p->tunnel_list)) - break; + while (!list_empty(&set->pools)) { + p = list_first_entry(&set->pools, typeof(*p), entry); + list_del(&p->entry); + free_pool(p); + } + if (set->def_pool) + free_pool(set->def_pool); + _free(set); +} - addr[i] = list_entry(p->tunnel_list.next, typeof(*addr[i]), entry); - list_del(&addr[i]->entry); +static struct pool_set *build_pool_set(void) +{ + struct conf_sect_t *s = conf_get_section("ip-pool"); + struct conf_option_t *opt; + struct pool_set *set; + struct ip_pool *p; + + set = _malloc(sizeof(*set)); + if (!set) + return NULL; + memset(set, 0, sizeof(*set)); + INIT_LIST_HEAD(&set->pools); + set->orphan_policy = ORPHAN_KEEP; + +#ifdef RADIUS + /* statics persist across reloads; reset to defaults so a removed + * vendor/attr line doesn't leave stale values behind */ + conf_vendor = 0; + conf_attr = 88; // Framed-Pool +#endif + + if (!s) + return set; /* no section: an empty (inert) set */ + + set->def_pool = create_pool(set, NULL); + if (!set->def_pool) + goto err; + + list_for_each_entry(opt, &s->items, entry) { + const char *range_str; + struct ip_pool *pool; + uint32_t step; + int gw_offset, peer_offset; + +#ifdef RADIUS + if (triton_module_loaded("radius")) { + if (!strcmp(opt->name, "vendor")) { + if (opt->val) + conf_vendor = parse_vendor_opt(opt->val); + continue; + } else if (!strcmp(opt->name, "attr")) { + if (opt->val) + conf_attr = parse_attr_opt(opt->val); + continue; + } + } +#endif + if (!strcmp(opt->name, "gw-ip-address")) { + parse_gw_ip_address(opt->val, &set->gw_ip_address); + continue; + } + if (!strcmp(opt->name, "shuffle")) { + set->shuffle = opt->val ? atoi(opt->val) : 0; + continue; + } + if (!strcmp(opt->name, "reload-orphan")) { + if (opt->val && !strcmp(opt->val, "disconnect")) + set->orphan_policy = ORPHAN_DISCONNECT; + else + set->orphan_policy = ORPHAN_KEEP; + continue; + } + if (!strcmp(opt->name, "gw")) { + /* deprecated/no-op: the per-block local address has long been + * overwritten at get time by gw-ip-address/0. Accept and ignore. */ + log_warn("ippool: 'gw=' is deprecated and ignored\n"); + continue; } - if (!addr[2]) - break; + if (!strcmp(opt->name, "tunnel")) + range_str = opt->val; + else if (!opt->val || strchr(opt->name, ',')) + range_str = opt->name; + else + continue; /* unrecognized option */ + if (!range_str) + continue; - it = malloc(sizeof(*it)); - if (!it) { - log_emerg("ippool: out of memory\n"); - break; + if (parse_line_opts(set, opt->raw, &pool, &step, &gw_offset, &peer_offset)) { + log_error("ippool: failed to parse '%s'\n", opt->raw); + continue; } - memset(it, 0, sizeof(*it)); - it->pool = p; - it->it.owner = &ipdb; - it->it.addr = addr[1]->addr; - it->it.peer_addr = addr[2]->addr; + add_range_to_pool(pool, range_str, step, gw_offset, peer_offset); + } + + if (finalize_ranges(set, set->def_pool)) + goto err; + list_for_each_entry(p, &set->pools, entry) { + if (finalize_ranges(set, p)) + goto err; + if (list_empty(&p->ranges)) + log_warn("ippool: pool '%s' is empty or not defined\n", p->name); + } + + return set; + +err: + free_pool_set(set); + return NULL; +} - list_add_tail(&it->entry, &p->items); +/* ===== address lookup across a set ===== */ - for (i = 0; i < 4; i++) { - if (addr[i]) - free(addr[i]); +static int pool_contains(struct ip_pool *p, uint32_t peer_host, + struct ip_range **out_r, uint64_t *out_bit) +{ + struct ip_range *r; + + list_for_each_entry(r, &p->ranges, entry) { + uint64_t bit = range_addr_to_bit(r, peer_host); + if (bit != BM_INVALID) { + *out_r = r; + *out_bit = bit; + return 1; } } + return 0; +} - for (i = 0; i < 4; i++) { - if (addr[i]) - free(addr[i]); +/* find the pool+range+bit owning `peer_host`, preferring `pref_name` on overlap */ +static int find_target(struct pool_set *set, uint32_t peer_host, const char *pref_name, + struct ip_pool **op, struct ip_range **orr, uint64_t *obit) +{ + struct ip_pool *p; + + if (!set) + return 0; + + if (pref_name) { + p = find_pool(set, pref_name, 0); + if (p && pool_contains(p, peer_host, orr, obit)) { + *op = p; + return 1; + } + } + + if (set->def_pool && pool_contains(set->def_pool, peer_host, orr, obit)) { + *op = set->def_pool; + return 1; + } + list_for_each_entry(p, &set->pools, entry) { + if (pool_contains(p, peer_host, orr, obit)) { + *op = p; + return 1; + } } + + return 0; } +/* ===== ipdb get/put ===== */ static struct ipv4db_item_t *get_ip(struct ap_session *ses) { - struct ippool_item_t *it; - struct ippool_t *pool, *start; + struct pool_set *set; + struct ip_pool *pool, *start, *found_p = NULL; + struct ip_range *r, *found_r = NULL; + struct ip_lease *lease; + uint64_t bit = BM_INVALID; + uint64_t rnd; + uint32_t base; + + pthread_rwlock_rdlock(&pool_set_rwlock); + set = cur_set; + if (!set) { + pthread_rwlock_unlock(&pool_set_rwlock); + return NULL; + } if (ses->ipv4_pool_name) - pool = find_pool(ses->ipv4_pool_name, 0); + pool = find_pool(set, ses->ipv4_pool_name, 0); else - pool = def_pool; + pool = set->def_pool; - if (!pool) + if (!pool) { + pthread_rwlock_unlock(&pool_set_rwlock); return NULL; + } + + /* draw randomness once, outside the lock: read(urandom_fd) is a + * blocking syscall and must not run while holding pool->lock */ + rnd = set->shuffle ? rand_u64() : 0; start = pool; do { spin_lock(&pool->lock); - if (!list_empty(&pool->items)) { - it = list_entry(pool->items.next, typeof(*it), entry); - list_del(&it->entry); - } else - it = NULL; + list_for_each_entry(r, &pool->ranges, entry) { + uint64_t from = set->shuffle ? (r->count ? rnd % r->count : 0) : r->cursor; + bit = bm_find_free(r->bitmap, r->count, from); + if (bit != BM_INVALID) { + bm_set(r->bitmap, bit); + r->used++; + r->cursor = bit + 1; + found_r = r; + found_p = pool; + break; + } + } spin_unlock(&pool->lock); + if (found_r) + break; + pool = pool->next; + } while (pool && pool != start); - if (it) { - if (ses->ctrl->ppp) - it->it.addr = conf_gw_ip_address; - else - it->it.addr = 0; + if (!found_r) { + pthread_rwlock_unlock(&pool_set_rwlock); + return NULL; + } - it->it.mask = 0; + lease = _malloc(sizeof(*lease)); + if (!lease) { + spin_lock(&found_p->lock); + bm_clear(found_r->bitmap, bit); + found_r->used--; + spin_unlock(&found_p->lock); + pthread_rwlock_unlock(&pool_set_rwlock); + return NULL; + } - return &it->it; - } + memset(lease, 0, sizeof(*lease)); + lease->pool = found_p; + lease->range = found_r; + lease->it.owner = &ipdb; + base = found_r->start + (uint32_t)bit * found_r->step; + lease->it.peer_addr = htonl(base + found_r->peer_offset); + lease->it.addr = ses->ctrl->ppp ? set->gw_ip_address : 0; + lease->it.mask = 0; - pool = pool->next; - } while (pool && pool != start); + pthread_rwlock_unlock(&pool_set_rwlock); - return NULL; + return &lease->it; } static void put_ip(struct ap_session *ses, struct ipv4db_item_t *it) { - struct ippool_item_t *pit = container_of(it, typeof(*pit), it); + struct ip_lease *lease = container_of(it, typeof(*lease), it); + + pthread_rwlock_rdlock(&pool_set_rwlock); + if (lease->pool && lease->range) { + uint64_t bit = range_addr_to_bit(lease->range, ntohl(it->peer_addr)); + spin_lock(&lease->pool->lock); + if (bit != BM_INVALID && bm_test(lease->range->bitmap, bit)) { + bm_clear(lease->range->bitmap, bit); + lease->range->used--; + } + spin_unlock(&lease->pool->lock); + } + /* else: orphaned by a reload - nothing to release, just free the wrapper */ + pthread_rwlock_unlock(&pool_set_rwlock); - spin_lock(&pit->pool->lock); - list_add_tail(&pit->entry, &pit->pool->items); - spin_unlock(&pit->pool->lock); + _free(lease); } static struct ipdb_t ipdb = { @@ -417,57 +726,53 @@ static int session_save(struct ap_session *ses, struct backup_mod *m) static int session_restore(struct ap_session *ses, struct backup_mod *m) { struct backup_tag *tag; - in_addr_t addr = 0, peer_addr; - struct ippool_t *p; - struct ippool_item_t *it, *it0 = NULL; + in_addr_t addr = 0, peer_addr = 0; + struct ip_pool *np; + struct ip_range *nr; + uint64_t bit; m = backup_find_mod(m->data, MODID_COMMON); list_for_each_entry(tag, &m->tag_list, entry) { switch (tag->id) { case SES_TAG_IPV4_ADDR: - addr = *(in_addr_t *)tag->data; + memcpy(&addr, tag->data, sizeof(addr)); break; case SES_TAG_IPV4_PEER_ADDR: - peer_addr = *(in_addr_t *)tag->data; + memcpy(&peer_addr, tag->data, sizeof(peer_addr)); break; } } - spin_lock(&def_pool->lock); - list_for_each_entry(it, &def_pool->items, entry) { - if (peer_addr == it->it.peer_addr && addr == it->it.addr) { - list_del(&it->entry); - it0 = it; - break; - } - } - spin_unlock(&def_pool->lock); - - if (!it0) { - list_for_each_entry(p, &pool_list, entry) { - spin_lock(&p->lock); - list_for_each_entry(it, &p->items, entry) { - if (peer_addr == it->it.peer_addr && addr == it->it.addr) { - list_del(&it->entry); - it0 = it; - break; - } + pthread_rwlock_rdlock(&pool_set_rwlock); + if (find_target(cur_set, ntohl(peer_addr), NULL, &np, &nr, &bit)) { + struct ip_lease *lease = _malloc(sizeof(*lease)); + if (lease) { + memset(lease, 0, sizeof(*lease)); + lease->pool = np; + lease->range = nr; + lease->it.owner = &ipdb; + lease->it.addr = addr; + lease->it.peer_addr = peer_addr; + spin_lock(&np->lock); + if (!bm_test(nr->bitmap, bit)) { + bm_set(nr->bitmap, bit); + nr->used++; } - spin_unlock(&p->lock); - if (it0) - break; + spin_unlock(&np->lock); + ses->ipv4 = &lease->it; } } + pthread_rwlock_unlock(&pool_set_rwlock); - if (it0) - ses->ipv4 = &it0->it; - else { + if (!ses->ipv4) { ses->ipv4 = _malloc(sizeof(*ses->ipv4)); - memset(ses->ipv4, 0, sizeof(*ses->ipv4)); - ses->ipv4->addr = addr; - ses->ipv4->peer_addr = peer_addr; - ses->ipv4->owner = &ipdb_b; + if (ses->ipv4) { + memset(ses->ipv4, 0, sizeof(*ses->ipv4)); + ses->ipv4->addr = addr; + ses->ipv4->peer_addr = peer_addr; + ses->ipv4->owner = &ipdb_b; + } } return 0; @@ -480,6 +785,101 @@ static struct backup_module backup_mod = { }; #endif +/* ===== reconcile on reload ===== */ + +static void reconcile_v4(struct pool_set *new_set, struct ap_session *ses, + int policy, struct list_head *disc) +{ + struct ipv4db_item_t *it = ses->ipv4; + uint32_t peer_host; + struct ip_pool *np; + struct ip_range *nr; + uint64_t bit; + + if (!it || !it->owner) + return; + + peer_host = ntohl(it->peer_addr); + + if (it->owner == &ipdb) { + struct ip_lease *lease = container_of(it, typeof(*lease), it); + if (find_target(new_set, peer_host, ses->ipv4_pool_name, &np, &nr, &bit)) { + spin_lock(&np->lock); + if (!bm_test(nr->bitmap, bit)) { + bm_set(nr->bitmap, bit); + nr->used++; + } + spin_unlock(&np->lock); + lease->pool = np; + lease->range = nr; + } else { + lease->pool = NULL; + lease->range = NULL; + if (policy == ORPHAN_DISCONNECT) { + struct disc_node *d = _malloc(sizeof(*d)); + if (d) { + d->ses = ses; + list_add_tail(&d->entry, disc); + } + } + } + } else { + /* foreign owner (radius/chap-secrets/static): reserve the bit so we + * never hand out a live address. We do not own or free it. */ + if (find_target(new_set, peer_host, NULL, &np, &nr, &bit)) { + spin_lock(&np->lock); + if (!bm_test(nr->bitmap, bit)) { + bm_set(nr->bitmap, bit); + nr->used++; + } + spin_unlock(&np->lock); + } + } +} + +static void terminate_orphan(void *arg) +{ + struct ap_session *ses = arg; + ap_session_terminate(ses, TERM_NAS_REBOOT, 0); +} + +static void load_config(void *data) +{ + struct pool_set *new_set, *old_set; + struct ap_session *ses; + struct disc_node *d; + LIST_HEAD(disc_list); + int policy; + + new_set = build_pool_set(); + if (!new_set) { + log_error("ippool: reload failed, keeping current pools\n"); + return; + } + policy = new_set->orphan_policy; + + pthread_rwlock_wrlock(&pool_set_rwlock); + pthread_rwlock_rdlock(&ses_lock); + + list_for_each_entry(ses, &ses_list, entry) + reconcile_v4(new_set, ses, policy, &disc_list); + + old_set = cur_set; + cur_set = new_set; + + pthread_rwlock_unlock(&ses_lock); + pthread_rwlock_unlock(&pool_set_rwlock); + + free_pool_set(old_set); + + while (!list_empty(&disc_list)) { + d = list_first_entry(&disc_list, typeof(*d), entry); + list_del(&d->entry); + triton_context_call(d->ses->ctrl->ctx, terminate_orphan, d->ses); + _free(d); + } +} + #ifdef RADIUS static int parse_attr(struct ap_session *ses, struct rad_attr_t *attr) { @@ -516,97 +916,70 @@ static void ev_radius_access_accept(struct ev_radius_t *ev) parse_attr(ev->ses, attr); } } +#endif -static int parse_attr_opt(const char *opt) -{ - struct rad_dict_attr_t *attr; - struct rad_dict_vendor_t *vendor; +/* ===== cli ===== */ - if (conf_vendor) - vendor = rad_dict_find_vendor_id(conf_vendor); - else - vendor = NULL; +static int show_ippool_exec(const char *cmd, char * const *fields, int fields_cnt, void *client) +{ + struct ip_pool *pool; + struct ip_range *r; + uint64_t total, used; - if (conf_vendor) { - if (vendor) - attr = rad_dict_find_vendor_attr(vendor, opt); - else - attr = NULL; - }else - attr = rad_dict_find_attr(opt); + cli_send(client, "IP Pool Usage Report\r\n"); + cli_send(client, "====================\r\n"); - if (attr) - return attr->id; + pthread_rwlock_rdlock(&pool_set_rwlock); + if (!cur_set) { + pthread_rwlock_unlock(&pool_set_rwlock); + return CLI_CMD_OK; + } - return atoi(opt); -} + if (cur_set->def_pool) { + pool = cur_set->def_pool; + total = used = 0; + spin_lock(&pool->lock); + list_for_each_entry(r, &pool->ranges, entry) { + total += r->count; + used += r->used; + } + spin_unlock(&pool->lock); + if (total > 0) + cli_sendv(client, "<default>\r\n total: %llu\r\n used: %llu\r\n available: %llu\r\n usage: %llu%%\r\n", + (unsigned long long)total, (unsigned long long)used, + (unsigned long long)(total - used), + (unsigned long long)(used * 100 / total)); + } -static int parse_vendor_opt(const char *opt) -{ - struct rad_dict_vendor_t *vendor; + list_for_each_entry(pool, &cur_set->pools, entry) { + if (!pool->name) + continue; + total = used = 0; + spin_lock(&pool->lock); + list_for_each_entry(r, &pool->ranges, entry) { + total += r->count; + used += r->used; + } + spin_unlock(&pool->lock); + if (total > 0) + cli_sendv(client, "%s\r\n total: %llu\r\n used: %llu\r\n available: %llu\r\n usage: %llu%%\r\n", + pool->name, (unsigned long long)total, (unsigned long long)used, + (unsigned long long)(total - used), + (unsigned long long)(used * 100 / total)); + } - vendor = rad_dict_find_vendor_name(opt); - if (vendor) - return vendor->id; + pthread_rwlock_unlock(&pool_set_rwlock); - return atoi(opt); + return CLI_CMD_OK; } -#endif -static int parse_options(const char *opt, struct ippool_t **pool, generate_func *generate, struct ippool_t **next) +static void show_ippool_help(char * const *fields, int fields_cnt, void *client) { - char *name, *ptr; - - name = strstr(opt, ",name="); - if (name) { - name += sizeof(",name=") - 1; - ptr = strchrnul(name, ','); - name = _strndup(name, ptr - name); - if (!name) - return -1; - *pool = find_pool(name, 1); - } else if ((name = strchr(opt, ',')) && !strchr(name + 1, '=')) { - name = _strdup(name + 1); - if (!name) - return -1; - *pool = find_pool(name, 1); - } else - *pool = def_pool; - - name = strstr(opt, ",allocator="); - if (name) { - name += sizeof(",allocator=") - 1; - ptr = strchrnul(name, ','); - name = strncpy(alloca(ptr - name + 1), name, ptr - name + 1); - if (strcmp(name, "p2p") == 0) - *generate = generate_pool_p2p; - else if (strcmp(name, "net30") == 0) - *generate = generate_pool_net30; - else { - log_error("ipool: '%s': unknown allocator\n", opt); - return -1; - } - } else - *generate = generate_pool_p2p; - - name = strstr(opt, ",next="); - if (name) { - name += sizeof(",next=") - 1; - ptr = strchrnul(name, ','); - name = strncpy(alloca(ptr - name + 1), name, ptr - name + 1); - *next = find_pool(name, 0); - if (!*next) { - name = _strdup(name); - if (!name) - return -1; - *next = find_pool(name, 1); - } - } else - *next = NULL; - - return 0; + cli_send(client, "show ippool - shows IP pool statistics\r\n"); } +/* ===== init ===== */ + static void ippool_init1(void) { ipdb_register(&ipdb); @@ -614,59 +987,11 @@ static void ippool_init1(void) static void ippool_init2(void) { - struct conf_sect_t *s = conf_get_section("ip-pool"); - struct conf_option_t *opt; - struct ippool_t *pool, *next; - generate_func generate; + load_config(NULL); - if (!s) - return; - - def_pool = create_pool(NULL); - - list_for_each_entry(opt, &s->items, entry) { -#ifdef RADIUS - if (triton_module_loaded("radius")) { - if (!strcmp(opt->name, "vendor")) { - conf_vendor = parse_vendor_opt(opt->val); - continue; - } else if (!strcmp(opt->name, "attr")) { - conf_attr = parse_attr_opt(opt->val); - continue; - } - } -#endif - if (!strcmp(opt->name, "gw-ip-address")) - parse_gw_ip_address(opt->val); - else if (!strcmp(opt->name, "shuffle")) - conf_shuffle = atoi(opt->val); - else { - if (parse_options(opt->raw, &pool, &generate, &next)) { - log_error("ippool: failed to parse '%s'\n", opt->raw); - continue; - } - - if (!strcmp(opt->name, "gw")) - add_range(pool, &pool->gw_list, opt->val, generate); - else if (!strcmp(opt->name, "tunnel")) - add_range(pool, &pool->tunnel_list, opt->val, generate); - else if (!opt->val || strchr(opt->name, ',')) - add_range(pool, &pool->tunnel_list, opt->name, generate); - - if (next) - pool->next = next; - } - } - - if (def_pool->generate) - def_pool->generate(def_pool); - - list_for_each_entry(pool, &pool_list, entry) { - if (pool->generate) - pool->generate(pool); - else - log_warn("ippool: pool '%s' is empty or not defined\n", pool->name); - } + if (triton_event_register_handler(EV_CONFIG_RELOAD, load_config) < 0) + log_error("ippool: registration of CONFIG_RELOAD event failed," + " pools will not reload\n"); #ifdef USE_BACKUP backup_register_module(&backup_mod); @@ -676,6 +1001,8 @@ static void ippool_init2(void) if (triton_module_loaded("radius")) triton_event_register_handler(EV_RADIUS_ACCESS_ACCEPT, (triton_event_func)ev_radius_access_accept); #endif + + cli_register_simple_cmd2(show_ippool_exec, show_ippool_help, 2, "show", "ippool"); } DEFINE_INIT(51, ippool_init1); diff --git a/accel-pppd/extra/ipv6pool.c b/accel-pppd/extra/ipv6pool.c index c5cabef5..05b84fe8 100644 --- a/accel-pppd/extra/ipv6pool.c +++ b/accel-pppd/extra/ipv6pool.c @@ -3,76 +3,112 @@ #include <stdint.h> #include <unistd.h> #include <string.h> -#include <unistd.h> #include <fcntl.h> +#include <pthread.h> #include <arpa/inet.h> +#include <netinet/in.h> #include <endian.h> +#include "triton.h" #include "events.h" #include "ipdb.h" #include "list.h" #include "log.h" #include "spinlock.h" +#include "ap_session.h" #ifdef RADIUS #include "radius.h" #endif +#include "bitpool.h" #include "memdebug.h" -enum ippool_type -{ - IPPOOL_ADDRESS, - IPPOOL_PREFIX +/* + * Bitmap IPv6 address (IA_NA) and delegated-prefix (IA_PD) pools. + * + * Same model as the IPv4 pool: each pool holds a list of contiguous ranges, + * each range owns one bitmap indexing per-lease prefixes (bit i -> start + + * i*2^(128-prefix_len)). A lease is a per-session malloc wrapper that embeds + * the single ipv6db_addr_t node linked into the item's addr/prefix list. The + * whole set is rebuilt and swapped on EV_CONFIG_RELOAD with a reconcile pass. + * + * TODO: a sparse/hierarchical allocator would lift IPPOOL_MAX_BITS; the dense + * bitmap requires (prefix_len - mask) <= 24, i.e. <= ~16.7M prefixes per range. + */ + +#define IPPOOL_MAX_BITS (1u << 24) +#define IPPOOL_MAX_SHIFT 24 /* prefix_len - mask cap (2^24 entries) */ + +enum ippool_type { + IPPOOL_ADDRESS, + IPPOOL_PREFIX, }; -struct ippool_t -{ +enum { + ORPHAN_KEEP = 0, + ORPHAN_DISCONNECT, +}; + +struct ip6_range { struct list_head entry; - char *name; - struct list_head gw_list; - struct list_head items; - struct ippool_t *next; - spinlock_t lock; + struct in6_addr start; + int prefix_len; + int shift; /* 128 - prefix_len */ + uint64_t count; /* number of prefixes = bits */ + uint64_t cursor; + uint64_t used; + bm_word_t *bitmap; }; -struct ippool_item_t -{ +struct ip6_pool { struct list_head entry; - struct ippool_t *pool; - struct ipv6db_item_t it; + char *name; /* NULL for default pool */ + struct ip6_pool *next; + struct list_head ranges; + spinlock_t lock; }; -struct dppool_item_t -{ +struct pool_set { + struct list_head ippools; /* named NA pools */ + struct list_head dppools; /* named PD pools */ + struct ip6_pool *def_ippool; + struct ip6_pool *def_dppool; + struct in6_addr gw_addr; + int orphan_policy; +}; + +struct ip6_lease { + struct ip6_pool *pool; + struct ip6_range *range; + struct ipv6db_item_t it; /* ses->ipv6 = &it */ + struct ipv6db_addr_t node; /* linked into it.addr_list */ +}; + +struct dp_lease { + struct ip6_pool *pool; + struct ip6_range *range; + struct ipv6db_prefix_t it; /* ses->ipv6_dp = &it */ + struct ipv6db_addr_t node; /* linked into it.prefix_list */ +}; + +struct disc_node { struct list_head entry; - struct ippool_t *pool; - struct ipv6db_prefix_t it; + struct ap_session *ses; }; +static struct ipdb_t ipdb; + +static pthread_rwlock_t pool_set_rwlock = PTHREAD_RWLOCK_INITIALIZER; +static struct pool_set *cur_set; + #ifdef RADIUS static int conf_vendor = 0; static int conf_dppool_attr = 171; // Delegated-IPv6-Prefix-Pool static int conf_ippool_attr = 172; // Stateful-IPv6-Address-Pool #endif -static LIST_HEAD(ippool_list); -static LIST_HEAD(dppool_list); -static struct ipdb_t ipdb; -static struct in6_addr conf_gw_addr; -static struct ippool_t *def_ippool; -static struct ippool_t *def_dppool; - -static void in6_addr_add(struct in6_addr *res, const struct in6_addr *arg) -{ - uint16_t n = 0; - int i; - - for (i = 15; i >= 0; i--) { - n = (uint16_t)res->s6_addr[i] + arg->s6_addr[i] + (n >> 8); - res->s6_addr[i] = n & 0xff; - } -} +/* ===== 128-bit helpers (big-endian s6_addr[16]) ===== */ static int in6_addr_cmp(const struct in6_addr *n1, const struct in6_addr *n2) { @@ -88,154 +124,212 @@ static int in6_addr_cmp(const struct in6_addr *n1, const struct in6_addr *n2) return 0; } -static struct ippool_t *create_pool(enum ippool_type type, char *name) +/* o = a - b (assumes a >= b) */ +static void in6_sub(uint8_t *o, const uint8_t *a, const uint8_t *b) { - struct ippool_t *pool = malloc(sizeof(*pool)); - struct list_head *pool_list = (type == IPPOOL_PREFIX) ? &dppool_list : &ippool_list; + int i, borrow = 0; - memset(pool, 0, sizeof(*pool)); - pool->name = name; + for (i = 15; i >= 0; i--) { + int v = (int)a[i] - b[i] - borrow; + if (v < 0) { + v += 256; + borrow = 1; + } else + borrow = 0; + o[i] = v; + } +} - INIT_LIST_HEAD(&pool->items); - spinlock_init(&pool->lock); +/* floor(delta >> shift) into *out; returns -1 if it doesn't fit in uint64 */ +static int in6_shr_u64(const uint8_t *d, int shift, uint64_t *out) +{ + int p; + uint64_t v = 0; - if (name) - list_add_tail(&pool->entry, pool_list); + for (p = 0; p < 64; p++) { + int sp = shift + p; + if (sp < 128 && (d[15 - (sp >> 3)] & (1 << (sp & 7)))) + v |= (uint64_t)1 << p; + } + for (p = shift + 64; p < 128; p++) { + if (d[15 - (p >> 3)] & (1 << (p & 7))) + return -1; + } - return pool; + *out = v; + return 0; } -static struct ippool_t *find_pool(enum ippool_type type, char *name, int create) +/* out = start + (i << shift) */ +static void in6_index_to_addr(struct in6_addr *out, const struct in6_addr *start, + uint64_t i, int shift) { - struct ippool_t *pool; - struct list_head *pool_list = (type == IPPOOL_PREFIX) ? &dppool_list : &ippool_list; - - list_for_each_entry(pool, pool_list, entry) { - if (!strcmp(pool->name, name)) - return pool; + uint8_t add[16] = { 0 }; + int p, b, carry = 0; + + for (p = 0; p < 64; p++) { + if (i & ((uint64_t)1 << p)) { + int sp = shift + p; + if (sp < 128) + add[15 - (sp >> 3)] |= (1 << (sp & 7)); + } } - if (create) - return create_pool(type, name); - - return NULL; + memcpy(out, start, 16); + for (b = 15; b >= 0; b--) { + int s = out->s6_addr[b] + add[b] + carry; + out->s6_addr[b] = s & 0xff; + carry = s >> 8; + } } -static void generate_ippool(struct ippool_t *pool, struct in6_addr *addr, int mask, int prefix_len) +/* bit index of a leased prefix within a range, or BM_INVALID */ +static uint64_t range_addr_to_bit(const struct ip6_range *r, const struct in6_addr *addr) { - struct ippool_item_t *it; - struct ipv6db_addr_t *a; - struct in6_addr ip, end, step; + uint8_t delta[16]; + uint64_t i; + int p; - memcpy(&ip, addr, sizeof(ip)); + if (in6_addr_cmp(addr, &r->start) < 0) + return BM_INVALID; - memcpy(&end, addr, sizeof(end)); - if (mask > 64) - *(uint64_t *)(end.s6_addr + 8) = htobe64(be64toh(*(uint64_t *)(end.s6_addr + 8)) | ((1llu << (128 - mask)) - 1)); - else { - memset(end.s6_addr + 8, 0xff, 8); - *(uint64_t *)end.s6_addr = htobe64(be64toh(*(uint64_t *)end.s6_addr) | ((1llu << (64 - mask)) - 1)); + in6_sub(delta, addr->s6_addr, r->start.s6_addr); + + /* must sit on a prefix boundary: low `shift` bits zero */ + for (p = 0; p < r->shift; p++) { + if (delta[15 - (p >> 3)] & (1 << (p & 7))) + return BM_INVALID; } - memset(&step, 0, sizeof(step)); - if (prefix_len > 64) - *(uint64_t *)(step.s6_addr + 8) = htobe64(1llu << (128 - prefix_len)); - else - *(uint64_t *)step.s6_addr = htobe64(1llu << (64 - prefix_len)); + if (in6_shr_u64(delta, r->shift, &i)) + return BM_INVALID; + if (i >= r->count) + return BM_INVALID; - while (in6_addr_cmp(&ip, &end) <= 0) { - it = malloc(sizeof(*it)); - memset(it, 0, sizeof(*it)); - it->pool = pool; - it->it.owner = &ipdb; - INIT_LIST_HEAD(&it->it.addr_list); - a = malloc(sizeof(*a)); - memset(a, 0, sizeof(*a)); - memcpy(&a->addr, &ip, sizeof(ip)); - a->prefix_len = prefix_len; - list_add_tail(&a->entry, &it->it.addr_list); - list_add_tail(&it->entry, &pool->items); - in6_addr_add(&ip, &step); - } + return i; } -static void generate_dppool(struct ippool_t *pool, struct in6_addr *addr, int mask, int prefix_len) +/* ===== pool set construction ===== */ + +static struct ip6_pool *create_pool(struct list_head *pool_list, char *name) { - struct dppool_item_t *it; - struct in6_addr ip, end, step; - struct ipv6db_addr_t *a; + struct ip6_pool *p = _malloc(sizeof(*p)); - memcpy(&ip, addr, sizeof(ip)); + if (!p) + return NULL; - memcpy(&end, addr, sizeof(end)); - if (mask > 64) - *(uint64_t *)(end.s6_addr + 8) = htobe64(be64toh(*(uint64_t *)(end.s6_addr + 8)) | ((1llu << (128 - mask)) - 1)); - else { - memset(end.s6_addr + 8, 0xff, 8); - *(uint64_t *)end.s6_addr = htobe64(be64toh(*(uint64_t *)end.s6_addr) | ((1llu << (64 - mask)) - 1)); - } + memset(p, 0, sizeof(*p)); + p->name = name; + INIT_LIST_HEAD(&p->ranges); + spinlock_init(&p->lock); - memset(&step, 0, sizeof(step)); - if (prefix_len > 64) - *(uint64_t *)(step.s6_addr + 8) = htobe64(1llu << (128 - prefix_len)); - else - *(uint64_t *)step.s6_addr = htobe64(1llu << (64 - prefix_len)); + if (name) + list_add_tail(&p->entry, pool_list); + + return p; +} - while (in6_addr_cmp(&ip, &end) <= 0) { - it = malloc(sizeof(*it)); - memset(it, 0, sizeof(*it)); - it->pool = pool; - it->it.owner = &ipdb; - INIT_LIST_HEAD(&it->it.prefix_list); - a = malloc(sizeof(*a)); - memset(a, 0, sizeof(*a)); - memcpy(&a->addr, &ip, sizeof(ip)); - a->prefix_len = prefix_len; - list_add_tail(&a->entry, &it->it.prefix_list); - list_add_tail(&it->entry, &pool->items); - in6_addr_add(&ip, &step); +static struct ip6_pool *find_pool(struct list_head *pool_list, const char *name, int create) +{ + struct ip6_pool *p; + + list_for_each_entry(p, pool_list, entry) { + if (p->name && !strcmp(p->name, name)) + return p; } + + if (create) { + char *dup = _strdup(name); + if (!dup) + return NULL; + return create_pool(pool_list, dup); + } + + return NULL; } -static void add_prefix(enum ippool_type type, struct ippool_t *pool, const char *_val) +/* parse "<addr>/<mask>,<prefix_len>" and append a range to `pool` */ +static void add_prefix(struct ip6_pool *pool, const char *_val) { char *val = _strdup(_val); char *ptr1, *ptr2; - struct in6_addr addr; - int prefix_len; - int mask; + struct in6_addr start, end; + int prefix_len, mask, shift; + uint64_t count; + struct ip6_range *r; + + if (!val) + return; ptr1 = strchr(val, '/'); if (!ptr1) goto err; - *ptr1 = 0; ptr2 = strchr(ptr1 + 1, ','); if (!ptr2) goto err; - *ptr2 = 0; - if (inet_pton(AF_INET6, val, &addr) == 0) + if (inet_pton(AF_INET6, val, &start) == 0) goto err; - if (sscanf(ptr1 + 1, "%i", &mask) != 1) goto err; - if (mask < 7 || mask > 127) goto err; - if (sscanf(ptr2 + 1, "%i", &prefix_len) != 1) goto err; - - if (prefix_len > 128 || prefix_len < mask) + if (prefix_len > 128 || prefix_len < mask) goto err; - if (type == IPPOOL_PREFIX) - generate_dppool(pool, &addr, mask, prefix_len); - else - generate_ippool(pool, &addr, mask, prefix_len); + if (prefix_len - mask > IPPOOL_MAX_SHIFT) { + log_error("ipv6_pool: range '%s' has 2^%d prefixes, exceeds cap 2^%d; skipping\n", + _val, prefix_len - mask, IPPOOL_MAX_SHIFT); + _free(val); + return; + } + + shift = 128 - prefix_len; + + /* end = start | hostmask(mask) (matches the original generator) */ + memcpy(&end, &start, sizeof(end)); + if (mask > 64) { + uint64_t value; + memcpy(&value, end.s6_addr + 8, sizeof(value)); + value = htobe64(be64toh(value) | ((1llu << (128 - mask)) - 1)); + memcpy(end.s6_addr + 8, &value, sizeof(value)); + } + else { + uint64_t value; + memset(end.s6_addr + 8, 0xff, 8); + memcpy(&value, end.s6_addr, sizeof(value)); + value = htobe64(be64toh(value) | ((1llu << (64 - mask)) - 1)); + memcpy(end.s6_addr, &value, sizeof(value)); + } + + { + uint8_t delta[16]; + uint64_t span; + in6_sub(delta, end.s6_addr, start.s6_addr); + if (in6_shr_u64(delta, shift, &span) || span >= IPPOOL_MAX_BITS) { + log_error("ipv6_pool: range '%s' exceeds cap; skipping\n", _val); + _free(val); + return; + } + count = span + 1; + } + + r = _malloc(sizeof(*r)); + if (!r) { + _free(val); + return; + } + memset(r, 0, sizeof(*r)); + memcpy(&r->start, &start, sizeof(start)); + r->prefix_len = prefix_len; + r->shift = shift; + r->count = count; + list_add_tail(&r->entry, &pool->ranges); _free(val); return; @@ -245,133 +339,130 @@ err: _free(val); } -static struct ipv6db_item_t *get_ip(struct ap_session *ses) +static int finalize_ranges(struct ip6_pool *p) { - struct ippool_item_t *it; - struct ipv6db_addr_t *a; - struct ippool_t *pool, *start; - - if (ses->ipv6_pool_name) - pool = find_pool(IPPOOL_ADDRESS, ses->ipv6_pool_name, 0); - else - pool = def_ippool; - - if (!pool) - return NULL; + struct ip6_range *r; - start = pool; - do { - spin_lock(&pool->lock); - if (!list_empty(&pool->items)) { - it = list_entry(pool->items.next, typeof(*it), entry); - list_del(&it->entry); - } else - it = NULL; - spin_unlock(&pool->lock); + list_for_each_entry(r, &p->ranges, entry) { + uint64_t nw = BM_NWORDS(r->count); + uint64_t b; - if (it) { - a = list_entry(it->it.addr_list.next, typeof(*a), entry); - if (a->prefix_len == 128) { - memcpy(&it->it.intf_id, conf_gw_addr.s6_addr + 8, 8); - memcpy(&it->it.peer_intf_id, a->addr.s6_addr + 8, 8); - } else { - it->it.intf_id = 0; - it->it.peer_intf_id = 0; - } - - return &it->it; - } - - pool = pool->next; - } while (pool && pool != start); - - return NULL; -} + r->bitmap = _malloc(nw * sizeof(bm_word_t)); + if (!r->bitmap) + return -1; + memset(r->bitmap, 0, nw * sizeof(bm_word_t)); -static void put_ip(struct ap_session *ses, struct ipv6db_item_t *it) -{ - struct ippool_item_t *pit = container_of(it, typeof(*pit), it); + for (b = r->count; b < nw * BM_WORD_BITS; b++) + bm_set(r->bitmap, b); + } - spin_lock(&pit->pool->lock); - list_add_tail(&pit->entry, &pit->pool->items); - spin_unlock(&pit->pool->lock); + return 0; } -static struct ipv6db_prefix_t *get_dp(struct ap_session *ses) +static int parse_line_opts(struct pool_set *set, enum ippool_type type, const char *opt, + struct ip6_pool **pool) { - struct dppool_item_t *it; - struct ippool_t *pool, *start; - - if (ses->dpv6_pool_name) - pool = find_pool(IPPOOL_PREFIX, ses->dpv6_pool_name, 0); - else - pool = def_dppool; + struct list_head *pool_list = (type == IPPOOL_PREFIX) ? &set->dppools : &set->ippools; + char *name, *ptr; - if (!pool) - return NULL; + name = strstr(opt, ",name="); + if (name) { + name += sizeof(",name=") - 1; + ptr = strchrnul(name, ','); + name = _strndup(name, ptr - name); + if (!name) + return -1; + *pool = find_pool(pool_list, name, 1); + _free(name); + } else + *pool = (type == IPPOOL_PREFIX) ? set->def_dppool : set->def_ippool; - start = pool; - do { - spin_lock(&pool->lock); - if (!list_empty(&pool->items)) { - it = list_entry(pool->items.next, typeof(*it), entry); - list_del(&it->entry); - } else - it = NULL; - spin_unlock(&pool->lock); + if (!*pool) + return -1; - if (it) - return &it->it; + name = strstr(opt, ",next="); + if (name) { + struct ip6_pool *next; + name += sizeof(",next=") - 1; + ptr = strchrnul(name, ','); + name = _strndup(name, ptr - name); + if (!name) + return -1; + next = find_pool(pool_list, name, 1); + _free(name); + if (next) + (*pool)->next = next; + } - pool = pool->next; - } while (pool && pool != start); + return 0; +} - return NULL; +static void free_pool(struct ip6_pool *p) +{ + struct ip6_range *r; + + while (!list_empty(&p->ranges)) { + r = list_first_entry(&p->ranges, typeof(*r), entry); + list_del(&r->entry); + if (r->bitmap) + _free(r->bitmap); + _free(r); + } + if (p->name) + _free(p->name); + _free(p); } -static void put_dp(struct ap_session *ses, struct ipv6db_prefix_t *it) +static void free_pool_list(struct list_head *l) { - struct dppool_item_t *pit = container_of(it, typeof(*pit), it); + struct ip6_pool *p; - spin_lock(&pit->pool->lock); - list_add_tail(&pit->entry, &pit->pool->items); - spin_unlock(&pit->pool->lock); + while (!list_empty(l)) { + p = list_first_entry(l, typeof(*p), entry); + list_del(&p->entry); + free_pool(p); + } } -static struct ipdb_t ipdb = { - .get_ipv6 = get_ip, - .put_ipv6 = put_ip, - .get_ipv6_prefix = get_dp, - .put_ipv6_prefix = put_dp, -}; - -#ifdef RADIUS -static void ev_radius_access_accept(struct ev_radius_t *ev) +static void free_pool_set(struct pool_set *set) { - struct rad_attr_t *attr; - struct ap_session *ses = ev->ses; + if (!set) + return; - list_for_each_entry(attr, &ev->reply->attrs, entry) { - if (attr->attr->type != ATTR_TYPE_STRING) - continue; - if (attr->vendor && attr->vendor->id != conf_vendor) - continue; - if (!attr->vendor && conf_vendor) - continue; + free_pool_list(&set->ippools); + free_pool_list(&set->dppools); + if (set->def_ippool) + free_pool(set->def_ippool); + if (set->def_dppool) + free_pool(set->def_dppool); + _free(set); +} - if (conf_dppool_attr && conf_dppool_attr == attr->attr->id) { - if (ses->dpv6_pool_name) - _free(ses->dpv6_pool_name); - ses->dpv6_pool_name = _strdup(attr->val.string); - } else - if (conf_ippool_attr && conf_ippool_attr == attr->attr->id) { - if (ses->ipv6_pool_name) - _free(ses->ipv6_pool_name); - ses->ipv6_pool_name = _strdup(attr->val.string); - } +static int finalize_set(struct pool_set *set) +{ + struct ip6_pool *p; + + if (set->def_ippool && finalize_ranges(set->def_ippool)) + return -1; + if (set->def_dppool && finalize_ranges(set->def_dppool)) + return -1; + list_for_each_entry(p, &set->ippools, entry) { + if (finalize_ranges(p)) + return -1; + if (list_empty(&p->ranges)) + log_warn("ipv6_pool: pool '%s' is empty or not defined\n", p->name); } + list_for_each_entry(p, &set->dppools, entry) { + if (finalize_ranges(p)) + return -1; + if (list_empty(&p->ranges)) + log_warn("ipv6_pool: delegate pool '%s' is empty or not defined\n", p->name); + } + + return 0; } +#ifdef RADIUS static int parse_attr_opt(const char *opt) { struct rad_dict_attr_t *attr; @@ -408,83 +499,77 @@ static int parse_vendor_opt(const char *opt) } #endif -static int parse_options(enum ippool_type type, const char *opt, struct ippool_t **pool, struct ippool_t **next) -{ - char *name, *ptr; - - name = strstr(opt, ",name="); - if (name) { - name += sizeof(",name=") - 1; - ptr = strchrnul(name, ','); - name = _strndup(name, ptr - name); - if (!name) - return -1; - *pool = find_pool(type, name, 1); - } else if (type == IPPOOL_PREFIX) - *pool = def_dppool; - else - *pool = def_ippool; - - name = strstr(opt, ",next="); - if (name) { - name += sizeof(",next=") - 1; - ptr = strchrnul(name, ','); - name = strncpy(alloca(ptr - name + 1), name, ptr - name + 1); - *next = find_pool(type, name, 0); - if (!*next) { - name = _strdup(name); - if (!name) - return -1; - *next = find_pool(type, name, 1); - } - } else - *next = NULL; - - return 0; -} - -static void ippool_init1(void) -{ - ipdb_register(&ipdb); -} - -static void ippool_init2(void) +static struct pool_set *build_pool_set(void) { struct conf_sect_t *s = conf_get_section("ipv6-pool"); struct conf_option_t *opt; - struct ippool_t *pool, *next; - char *val; - enum ippool_type type; + struct pool_set *set; #ifdef RADIUS int dppool_attr = 0, ippool_attr = 0; #endif + set = _malloc(sizeof(*set)); + if (!set) + return NULL; + memset(set, 0, sizeof(*set)); + INIT_LIST_HEAD(&set->ippools); + INIT_LIST_HEAD(&set->dppools); + set->orphan_policy = ORPHAN_KEEP; + +#ifdef RADIUS + /* statics persist across reloads; reset to defaults so a removed + * vendor/attr line doesn't leave stale values behind. A stale + * conf_vendor would otherwise force conf_dppool_attr/conf_ippool_attr + * to 0 below, silently disabling pool-name matching. */ + conf_vendor = 0; + conf_dppool_attr = 171; // Delegated-IPv6-Prefix-Pool + conf_ippool_attr = 172; // Stateful-IPv6-Address-Pool +#endif + if (!s) - return; + return set; - def_ippool = create_pool(IPPOOL_ADDRESS, NULL); - def_dppool = create_pool(IPPOOL_PREFIX, NULL); + set->def_ippool = create_pool(&set->ippools, NULL); + set->def_dppool = create_pool(&set->dppools, NULL); + if (!set->def_ippool || !set->def_dppool) + goto err; list_for_each_entry(opt, &s->items, entry) { + enum ippool_type type; + const char *val; + struct ip6_pool *pool; + #ifdef RADIUS if (triton_module_loaded("radius")) { if (!strcmp(opt->name, "vendor")) { - conf_vendor = parse_vendor_opt(opt->val); + if (opt->val) + conf_vendor = parse_vendor_opt(opt->val); continue; } else if (!strcmp(opt->name, "attr-prefix")) { - dppool_attr = parse_attr_opt(opt->val); + if (opt->val) + dppool_attr = parse_attr_opt(opt->val); continue; } else if (!strcmp(opt->name, "attr-address")) { - ippool_attr = parse_attr_opt(opt->val); + if (opt->val) + ippool_attr = parse_attr_opt(opt->val); continue; } } #endif if (!strcmp(opt->name, "gw-ip6-address")) { - if (inet_pton(AF_INET6, opt->val, &conf_gw_addr) == 0) + if (opt->val && inet_pton(AF_INET6, opt->val, &set->gw_addr) == 0) log_error("ipv6_pool: failed to parse '%s'\n", opt->raw); continue; - } else if (!strcmp(opt->name, "delegate")) { + } + if (!strcmp(opt->name, "reload-orphan")) { + if (opt->val && !strcmp(opt->val, "disconnect")) + set->orphan_policy = ORPHAN_DISCONNECT; + else + set->orphan_policy = ORPHAN_KEEP; + continue; + } + + if (!strcmp(opt->name, "delegate")) { type = IPPOOL_PREFIX; val = opt->val; } else { @@ -492,24 +577,15 @@ static void ippool_init2(void) val = opt->name; } - if (parse_options(type, opt->raw, &pool, &next)) { + if (!val) + continue; + + if (parse_line_opts(set, type, opt->raw, &pool)) { log_error("ipv6_pool: failed to parse '%s'\n", opt->raw); continue; } - add_prefix(type, pool, val); - - if (next) - pool->next = next; - } - - list_for_each_entry(pool, &ippool_list, entry) { - if (list_empty(&pool->items)) - log_warn("ipv6_pool: pool '%s' is empty or not defined\n", pool->name); - } - list_for_each_entry(pool, &dppool_list, entry) { - if (list_empty(&pool->items)) - log_warn("ipv6_pool: delegate pool '%s' is empty or not defined\n", pool->name); + add_prefix(pool, val); } #ifdef RADIUS @@ -518,9 +594,435 @@ static void ippool_init2(void) conf_dppool_attr = dppool_attr; if (conf_vendor || ippool_attr) conf_ippool_attr = ippool_attr; - triton_event_register_handler(EV_RADIUS_ACCESS_ACCEPT, (triton_event_func)ev_radius_access_accept); } #endif + + if (finalize_set(set)) + goto err; + + return set; + +err: + free_pool_set(set); + return NULL; +} + +/* ===== address lookup ===== */ + +static int pool_contains(struct ip6_pool *p, const struct in6_addr *addr, + struct ip6_range **out_r, uint64_t *out_bit) +{ + struct ip6_range *r; + + list_for_each_entry(r, &p->ranges, entry) { + uint64_t bit = range_addr_to_bit(r, addr); + if (bit != BM_INVALID) { + *out_r = r; + *out_bit = bit; + return 1; + } + } + return 0; +} + +static int find_target(struct list_head *pool_list, struct ip6_pool *def_pool, + const struct in6_addr *addr, const char *pref_name, + struct ip6_pool **op, struct ip6_range **orr, uint64_t *obit) +{ + struct ip6_pool *p; + + if (pref_name) { + p = find_pool(pool_list, pref_name, 0); + if (p && pool_contains(p, addr, orr, obit)) { + *op = p; + return 1; + } + } + + if (def_pool && pool_contains(def_pool, addr, orr, obit)) { + *op = def_pool; + return 1; + } + list_for_each_entry(p, pool_list, entry) { + if (pool_contains(p, addr, orr, obit)) { + *op = p; + return 1; + } + } + + return 0; +} + +/* alloc a free bit from a pool's next-chain; returns range+bit+pool or NULL */ +static struct ip6_range *alloc_from(struct ip6_pool *pool, struct ip6_pool **found_p, uint64_t *found_bit) +{ + struct ip6_pool *start = pool; + struct ip6_range *r, *found_r = NULL; + uint64_t bit = BM_INVALID; + + do { + spin_lock(&pool->lock); + list_for_each_entry(r, &pool->ranges, entry) { + bit = bm_find_free(r->bitmap, r->count, r->cursor); + if (bit != BM_INVALID) { + bm_set(r->bitmap, bit); + r->used++; + r->cursor = bit + 1; + found_r = r; + *found_p = pool; + *found_bit = bit; + break; + } + } + spin_unlock(&pool->lock); + if (found_r) + return found_r; + pool = pool->next; + } while (pool && pool != start); + + return NULL; +} + +/* ===== ipdb get/put: NA ===== */ + +static struct ipv6db_item_t *get_ip(struct ap_session *ses) +{ + struct pool_set *set; + struct ip6_pool *pool, *found_p = NULL; + struct ip6_range *r; + struct ip6_lease *lease; + uint64_t bit; + + pthread_rwlock_rdlock(&pool_set_rwlock); + set = cur_set; + if (!set) { + pthread_rwlock_unlock(&pool_set_rwlock); + return NULL; + } + + pool = ses->ipv6_pool_name ? find_pool(&set->ippools, ses->ipv6_pool_name, 0) : set->def_ippool; + if (!pool) { + pthread_rwlock_unlock(&pool_set_rwlock); + return NULL; + } + + r = alloc_from(pool, &found_p, &bit); + if (!r) { + pthread_rwlock_unlock(&pool_set_rwlock); + return NULL; + } + + lease = _malloc(sizeof(*lease)); + if (!lease) { + spin_lock(&found_p->lock); + bm_clear(r->bitmap, bit); + r->used--; + spin_unlock(&found_p->lock); + pthread_rwlock_unlock(&pool_set_rwlock); + return NULL; + } + + memset(lease, 0, sizeof(*lease)); + lease->pool = found_p; + lease->range = r; + lease->it.owner = &ipdb; + INIT_LIST_HEAD(&lease->it.addr_list); + in6_index_to_addr(&lease->node.addr, &r->start, bit, r->shift); + lease->node.prefix_len = r->prefix_len; + list_add_tail(&lease->node.entry, &lease->it.addr_list); + + if (r->prefix_len == 128) { + memcpy(&lease->it.intf_id, set->gw_addr.s6_addr + 8, 8); + memcpy(&lease->it.peer_intf_id, lease->node.addr.s6_addr + 8, 8); + } else { + lease->it.intf_id = 0; + lease->it.peer_intf_id = 0; + } + + pthread_rwlock_unlock(&pool_set_rwlock); + + return &lease->it; +} + +static void put_ip(struct ap_session *ses, struct ipv6db_item_t *it) +{ + struct ip6_lease *lease = container_of(it, typeof(*lease), it); + + pthread_rwlock_rdlock(&pool_set_rwlock); + if (lease->pool && lease->range) { + uint64_t bit = range_addr_to_bit(lease->range, &lease->node.addr); + spin_lock(&lease->pool->lock); + if (bit != BM_INVALID && bm_test(lease->range->bitmap, bit)) { + bm_clear(lease->range->bitmap, bit); + lease->range->used--; + } + spin_unlock(&lease->pool->lock); + } + pthread_rwlock_unlock(&pool_set_rwlock); + + _free(lease); +} + +/* ===== ipdb get/put: PD ===== */ + +static struct ipv6db_prefix_t *get_dp(struct ap_session *ses) +{ + struct pool_set *set; + struct ip6_pool *pool, *found_p = NULL; + struct ip6_range *r; + struct dp_lease *lease; + uint64_t bit; + + pthread_rwlock_rdlock(&pool_set_rwlock); + set = cur_set; + if (!set) { + pthread_rwlock_unlock(&pool_set_rwlock); + return NULL; + } + + pool = ses->dpv6_pool_name ? find_pool(&set->dppools, ses->dpv6_pool_name, 0) : set->def_dppool; + if (!pool) { + pthread_rwlock_unlock(&pool_set_rwlock); + return NULL; + } + + r = alloc_from(pool, &found_p, &bit); + if (!r) { + pthread_rwlock_unlock(&pool_set_rwlock); + return NULL; + } + + lease = _malloc(sizeof(*lease)); + if (!lease) { + spin_lock(&found_p->lock); + bm_clear(r->bitmap, bit); + r->used--; + spin_unlock(&found_p->lock); + pthread_rwlock_unlock(&pool_set_rwlock); + return NULL; + } + + memset(lease, 0, sizeof(*lease)); + lease->pool = found_p; + lease->range = r; + lease->it.owner = &ipdb; + INIT_LIST_HEAD(&lease->it.prefix_list); + in6_index_to_addr(&lease->node.addr, &r->start, bit, r->shift); + lease->node.prefix_len = r->prefix_len; + list_add_tail(&lease->node.entry, &lease->it.prefix_list); + + pthread_rwlock_unlock(&pool_set_rwlock); + + return &lease->it; +} + +static void put_dp(struct ap_session *ses, struct ipv6db_prefix_t *it) +{ + struct dp_lease *lease = container_of(it, typeof(*lease), it); + + pthread_rwlock_rdlock(&pool_set_rwlock); + if (lease->pool && lease->range) { + uint64_t bit = range_addr_to_bit(lease->range, &lease->node.addr); + spin_lock(&lease->pool->lock); + if (bit != BM_INVALID && bm_test(lease->range->bitmap, bit)) { + bm_clear(lease->range->bitmap, bit); + lease->range->used--; + } + spin_unlock(&lease->pool->lock); + } + pthread_rwlock_unlock(&pool_set_rwlock); + + _free(lease); +} + +static struct ipdb_t ipdb = { + .get_ipv6 = get_ip, + .put_ipv6 = put_ip, + .get_ipv6_prefix = get_dp, + .put_ipv6_prefix = put_dp, +}; + +/* ===== reconcile on reload ===== */ + +static void reserve_bit(struct ip6_pool *np, struct ip6_range *nr, uint64_t bit) +{ + spin_lock(&np->lock); + if (!bm_test(nr->bitmap, bit)) { + bm_set(nr->bitmap, bit); + nr->used++; + } + spin_unlock(&np->lock); +} + +static void reconcile_na(struct pool_set *new_set, struct ap_session *ses, + int policy, struct list_head *disc) +{ + struct ipv6db_item_t *it = ses->ipv6; + struct ip6_pool *np; + struct ip6_range *nr; + uint64_t bit; + + if (!it || !it->owner) + return; + + if (it->owner == &ipdb) { + struct ip6_lease *lease = container_of(it, typeof(*lease), it); + if (find_target(&new_set->ippools, new_set->def_ippool, &lease->node.addr, + ses->ipv6_pool_name, &np, &nr, &bit)) { + reserve_bit(np, nr, bit); + lease->pool = np; + lease->range = nr; + } else { + lease->pool = NULL; + lease->range = NULL; + if (policy == ORPHAN_DISCONNECT) { + struct disc_node *d = _malloc(sizeof(*d)); + if (d) { + d->ses = ses; + list_add_tail(&d->entry, disc); + } + } + } + } else { + struct ipv6db_addr_t *a; + list_for_each_entry(a, &it->addr_list, entry) { + if (find_target(&new_set->ippools, new_set->def_ippool, &a->addr, + NULL, &np, &nr, &bit)) + reserve_bit(np, nr, bit); + } + } +} + +static void reconcile_pd(struct pool_set *new_set, struct ap_session *ses, + int policy, struct list_head *disc) +{ + struct ipv6db_prefix_t *it = ses->ipv6_dp; + struct ip6_pool *np; + struct ip6_range *nr; + uint64_t bit; + + if (!it || !it->owner) + return; + + if (it->owner == &ipdb) { + struct dp_lease *lease = container_of(it, typeof(*lease), it); + if (find_target(&new_set->dppools, new_set->def_dppool, &lease->node.addr, + ses->dpv6_pool_name, &np, &nr, &bit)) { + reserve_bit(np, nr, bit); + lease->pool = np; + lease->range = nr; + } else { + lease->pool = NULL; + lease->range = NULL; + if (policy == ORPHAN_DISCONNECT) { + struct disc_node *d = _malloc(sizeof(*d)); + if (d) { + d->ses = ses; + list_add_tail(&d->entry, disc); + } + } + } + } else { + struct ipv6db_addr_t *a; + list_for_each_entry(a, &it->prefix_list, entry) { + if (find_target(&new_set->dppools, new_set->def_dppool, &a->addr, + NULL, &np, &nr, &bit)) + reserve_bit(np, nr, bit); + } + } +} + +static void terminate_orphan(void *arg) +{ + struct ap_session *ses = arg; + ap_session_terminate(ses, TERM_NAS_REBOOT, 0); +} + +static void load_config(void *data) +{ + struct pool_set *new_set, *old_set; + struct ap_session *ses; + struct disc_node *d; + LIST_HEAD(disc_list); + int policy; + + new_set = build_pool_set(); + if (!new_set) { + log_error("ipv6_pool: reload failed, keeping current pools\n"); + return; + } + policy = new_set->orphan_policy; + + pthread_rwlock_wrlock(&pool_set_rwlock); + pthread_rwlock_rdlock(&ses_lock); + + list_for_each_entry(ses, &ses_list, entry) { + reconcile_na(new_set, ses, policy, &disc_list); + reconcile_pd(new_set, ses, policy, &disc_list); + } + + old_set = cur_set; + cur_set = new_set; + + pthread_rwlock_unlock(&ses_lock); + pthread_rwlock_unlock(&pool_set_rwlock); + + free_pool_set(old_set); + + while (!list_empty(&disc_list)) { + d = list_first_entry(&disc_list, typeof(*d), entry); + list_del(&d->entry); + triton_context_call(d->ses->ctrl->ctx, terminate_orphan, d->ses); + _free(d); + } +} + +#ifdef RADIUS +static void ev_radius_access_accept(struct ev_radius_t *ev) +{ + struct rad_attr_t *attr; + struct ap_session *ses = ev->ses; + + list_for_each_entry(attr, &ev->reply->attrs, entry) { + if (attr->attr->type != ATTR_TYPE_STRING) + continue; + if (attr->vendor && attr->vendor->id != conf_vendor) + continue; + if (!attr->vendor && conf_vendor) + continue; + + if (conf_dppool_attr && conf_dppool_attr == attr->attr->id) { + if (ses->dpv6_pool_name) + _free(ses->dpv6_pool_name); + ses->dpv6_pool_name = _strdup(attr->val.string); + } else if (conf_ippool_attr && conf_ippool_attr == attr->attr->id) { + if (ses->ipv6_pool_name) + _free(ses->ipv6_pool_name); + ses->ipv6_pool_name = _strdup(attr->val.string); + } + } +} +#endif + +/* ===== init ===== */ + +static void ippool_init1(void) +{ + ipdb_register(&ipdb); +} + +static void ippool_init2(void) +{ + load_config(NULL); + + if (triton_event_register_handler(EV_CONFIG_RELOAD, load_config) < 0) + log_error("ipv6_pool: registration of CONFIG_RELOAD event failed," + " pools will not reload\n"); + +#ifdef RADIUS + if (triton_module_loaded("radius")) + triton_event_register_handler(EV_RADIUS_ACCESS_ACCEPT, (triton_event_func)ev_radius_access_accept); +#endif } DEFINE_INIT(51, ippool_init1); diff --git a/accel-pppd/extra/logwtmp.c b/accel-pppd/extra/logwtmp.c index f95b62c4..f66e6403 100644 --- a/accel-pppd/extra/logwtmp.c +++ b/accel-pppd/extra/logwtmp.c @@ -14,6 +14,7 @@ #include "memdebug.h" +#ifdef HAVE_LOGWTMP static void ev_ses_started(struct ap_session *ses) { logwtmp(ses->ifname, ses->username ?: "", ses->ctrl->calling_station_id); @@ -29,5 +30,11 @@ static void init(void) triton_event_register_handler(EV_SES_STARTED, (triton_event_func)ev_ses_started); triton_event_register_handler(EV_SES_FINISHED, (triton_event_func)ev_ses_finished); } +#else +static void init(void) +{ + log_warn("logwtmp is not supported on your platfrom, check libc doc\n"); +} +#endif DEFINE_INIT(200, init); diff --git a/accel-pppd/extra/metrics.c b/accel-pppd/extra/metrics.c new file mode 100644 index 00000000..3d422289 --- /dev/null +++ b/accel-pppd/extra/metrics.c @@ -0,0 +1,1356 @@ +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <inttypes.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <pthread.h> +#include <time.h> +#include <unistd.h> +#include <arpa/inet.h> +#include <netinet/in.h> +#include <sys/socket.h> +#include <dlfcn.h> + +#include "triton.h" +#include "events.h" +#include "log.h" +#include "list.h" +#include "utils.h" +#include "ap_session.h" +#include "ipdb.h" +#include "version.h" + +#include "memdebug.h" + +/* Per-protocol session counters live in their respective shared modules. + * Resolve them with dlsym(RTLD_DEFAULT, ...) lazily rather than via direct + * (weak) references: when metrics is dlopen()ed before the protocol module + * the loader binds undefined refs to NULL and never updates them when a + * later RTLD_GLOBAL dlopen brings the symbols in. dlsym walks the live + * global scope at call time, so it picks them up regardless of order. + */ +typedef unsigned int (*proto_stat_fn)(void); + +struct proto_stat { + const char *module; + const char *starting_sym; + const char *active_sym; + proto_stat_fn starting; + proto_stat_fn active; +}; + +static struct proto_stat proto_stats[] = { + { "pppoe", "pppoe_stat_starting", "pppoe_stat_active" }, + { "l2tp", "l2tp_stat_starting", "l2tp_stat_active" }, + { "pptp", "pptp_stat_starting", "pptp_stat_active" }, + { "sstp", "sstp_stat_starting", "sstp_stat_active" }, + { "ipoe", "ipoe_stat_starting", "ipoe_stat_active" }, +}; + +static int proto_resolve(struct proto_stat *p) +{ + if (!triton_module_loaded(p->module)) + return 0; + if (!p->starting) + p->starting = (proto_stat_fn)(uintptr_t)dlsym(RTLD_DEFAULT, p->starting_sym); + if (!p->active) + p->active = (proto_stat_fn)(uintptr_t)dlsym(RTLD_DEFAULT, p->active_sym); + return p->starting && p->active; +} + +enum metrics_format { + METRICS_FORMAT_PROMETHEUS, + METRICS_FORMAT_JSON, +}; + +#define METRICS_RECV_BUF_SIZE 2048 +#define METRICS_HDR_RESERVE 256 /* room reserved for the response header */ +#define METRICS_DEFAULT_READ_TIMEOUT 5 /* seconds */ +#define METRICS_DEFAULT_MAX_CLIENTS 64 + +struct metrics_client_t { + struct list_head entry; + struct triton_md_handler_t hnd; + struct triton_timer_t timer; + struct sockaddr_in addr; + char *recv_buf; + int recv_pos; + char *xmit_buf; + int xmit_pos; + int xmit_len; + unsigned int disconnect:1; +}; + +struct metrics_acl_t { + struct list_head entry; + uint32_t net; /* host byte order */ + uint32_t mask; /* host byte order */ +}; + +static enum metrics_format conf_format = METRICS_FORMAT_PROMETHEUS; +/* TODO: Support simultaneous Prometheus and JSON output, selected by endpoint + * (for example, /metrics and /metrics.json) instead of a process-wide format. + */ +static char *conf_address; +static LIST_HEAD(conf_allowed); +static int conf_read_timeout = METRICS_DEFAULT_READ_TIMEOUT; +static int conf_max_clients = METRICS_DEFAULT_MAX_CLIENTS; +static int conf_sessions; + +#define METRICS_ACCEPT_BACKOFF 1 /* seconds */ +#define METRICS_ACCEPT_BATCH 16 /* max accept()s per serv_read tick */ + +static struct triton_context_t serv_ctx; +static struct triton_md_handler_t serv_hnd; +static struct triton_timer_t accept_resume_timer; +static LIST_HEAD(clients); +static unsigned int client_count; +static int serv_running; +static int accept_paused; + +static int parse_format(const char *opt, enum metrics_format *out) +{ + if (!strcasecmp(opt, "prometheus")) { + *out = METRICS_FORMAT_PROMETHEUS; + return 0; + } + if (!strcasecmp(opt, "json")) { + *out = METRICS_FORMAT_JSON; + return 0; + } + return -1; +} + +/* TODO: IPv6 support. The listener, the ACL, and every sockaddr below are + * IPv4-only for now; bracketed-host syntax ("[::1]:8080"), AF_INET6 sockets, + * and IPv6 CIDRs in allowed_ips are intentionally left for a follow-up. + */ +static int parse_listen_address(const char *str, struct sockaddr_in *addr) +{ + char *buf, *colon; + int port; + int ret = -1; + + buf = strdup(str); + if (!buf) + return -1; + + colon = strrchr(buf, ':'); + if (!colon) + goto out; + + *colon = 0; + port = atoi(colon + 1); + if (port <= 0 || port > 65535) + goto out; + + memset(addr, 0, sizeof(*addr)); + addr->sin_family = AF_INET; + addr->sin_port = htons(port); + if (buf[0] == 0 || !strcmp(buf, "*") || !strcmp(buf, "0.0.0.0")) + addr->sin_addr.s_addr = htonl(INADDR_ANY); + else if (inet_pton(AF_INET, buf, &addr->sin_addr) != 1) + goto out; + + ret = 0; +out: + free(buf); + return ret; +} + +static void free_acl(struct list_head *head) +{ + struct metrics_acl_t *acl; + + while (!list_empty(head)) { + acl = list_first_entry(head, typeof(*acl), entry); + list_del(&acl->entry); + _free(acl); + } +} + +/* Strip surrounding whitespace, optional matching single/double quotes, + * and any trailing comma. Returns NULL if the token becomes empty. + */ +static char *clean_token(char *s) +{ + char *end; + size_t len; + + while (*s == ' ' || *s == '\t') + s++; + + len = strlen(s); + while (len && (s[len - 1] == ' ' || s[len - 1] == '\t' || + s[len - 1] == ',' || s[len - 1] == '\r' || + s[len - 1] == '\n')) + s[--len] = 0; + + if (len >= 2 && ((s[0] == '"' && s[len - 1] == '"') || + (s[0] == '\'' && s[len - 1] == '\''))) { + s[len - 1] = 0; + s++; + len -= 2; + } + + while (*s == ' ' || *s == '\t') + s++; + end = s + strlen(s); + while (end > s && (end[-1] == ' ' || end[-1] == '\t')) + *--end = 0; + + return *s ? s : NULL; +} + +/* TODO: IPv6 support. Only IPv4 CIDR or a bare IPv4 address are accepted; + * IPv6 entries in allowed_ips are rejected at parse time. ip_allowed() and + * struct metrics_acl_t store the network in a 32-bit host-order word, so + * adding IPv6 here will also need the matching widening downstream. + */ +static int parse_acl_entry(const char *str, struct metrics_acl_t **out) +{ + struct metrics_acl_t *acl; + struct in_addr addr; + uint8_t prefix; + uint32_t mask; + + if (!u_parse_ip4cidr(str, &addr, &prefix)) { + /* Accept a bare IP as /32 */ + if (inet_pton(AF_INET, str, &addr) != 1) + return -1; + prefix = 32; + } + + acl = _malloc(sizeof(*acl)); + if (!acl) + return -1; + + mask = prefix ? (uint32_t)0xffffffffu << (32 - prefix) : 0; + acl->net = ntohl(addr.s_addr) & mask; + acl->mask = mask; + *out = acl; + return 0; +} + +/* Parse `allowed_ips` value. Accepts: + * "1.2.3.4/32, 5.6.7.0/24" + * ["1.2.3.4/32", "5.6.7.0/24"] + * On success the supplied list is populated and 0 is returned. Returns -1 on + * any parse error; in that case the partial list is freed. + */ +static int parse_allowed_ips(const char *value, struct list_head *list) +{ + char *buf, *p, *tok; + int ret = -1; + + if (!value || !*value) + return 0; + + buf = strdup(value); + if (!buf) + return -1; + + p = buf; + while (*p == ' ' || *p == '\t') + p++; + if (*p == '[') + p++; + { + size_t len = strlen(p); + while (len && (p[len - 1] == ' ' || p[len - 1] == '\t' || + p[len - 1] == ']' || p[len - 1] == '\r' || + p[len - 1] == '\n')) + p[--len] = 0; + } + + while ((tok = strsep(&p, ",")) != NULL) { + struct metrics_acl_t *acl; + char *clean = clean_token(tok); + + if (!clean) + continue; + if (parse_acl_entry(clean, &acl) < 0) { + log_error("metrics: invalid entry in allowed_ips: '%s'\n", clean); + free_acl(list); + goto out; + } + list_add_tail(&acl->entry, list); + } + + ret = 0; +out: + free(buf); + return ret; +} + +static int ip_allowed(uint32_t addr_nbo) +{ + struct metrics_acl_t *acl; + uint32_t addr; + + if (list_empty(&conf_allowed)) + return 1; + + addr = ntohl(addr_nbo); + list_for_each_entry(acl, &conf_allowed, entry) { + if ((addr & acl->mask) == acl->net) + return 1; + } + return 0; +} + +static int load_config(void) +{ + const char *opt; + enum metrics_format fmt = METRICS_FORMAT_PROMETHEUS; + char *address = NULL; + struct sockaddr_in dummy; + LIST_HEAD(new_allowed); + + opt = conf_get_opt("metrics", "format"); + if (opt && parse_format(opt, &fmt) < 0) { + log_error("metrics: unknown format '%s', expected 'prometheus' or 'json'\n", opt); + return -1; + } + + opt = conf_get_opt("metrics", "address"); + if (!opt) { + log_emerg("metrics: 'address' option is required (host:port)\n"); + return -1; + } + if (parse_listen_address(opt, &dummy) < 0) { + log_emerg("metrics: invalid address '%s', expected host:port\n", opt); + return -1; + } + address = _strdup(opt); + if (!address) { + log_emerg("metrics: out of memory while loading config\n"); + return -1; + } + + opt = conf_get_opt("metrics", "allowed_ips"); + if (opt && parse_allowed_ips(opt, &new_allowed) < 0) { + _free(address); + return -1; + } + + conf_format = fmt; + if (conf_address) + _free(conf_address); + conf_address = address; + + free_acl(&conf_allowed); + list_replace_init(&new_allowed, &conf_allowed); + + opt = conf_get_opt("metrics", "read_timeout"); + if (opt) { + int n = atoi(opt); + conf_read_timeout = n > 0 ? n : 0; + } + + opt = conf_get_opt("metrics", "max_clients"); + if (opt) { + int n = atoi(opt); + conf_max_clients = n > 0 ? n : 0; + } + + opt = conf_get_opt("metrics", "sessions"); + conf_sessions = opt ? atoi(opt) != 0 : 0; + + return 0; +} + +static const char *content_type(void) +{ + switch (conf_format) { + case METRICS_FORMAT_JSON: + return "application/json"; + case METRICS_FORMAT_PROMETHEUS: + default: + return "text/plain; version=0.0.4; charset=utf-8"; + } +} + +struct strbuf { + char *data; + size_t len; + size_t cap; + int oom; +}; + +static int strbuf_reserve(struct strbuf *sb, size_t want) +{ + size_t need = sb->len + want + 1; + size_t ncap; + char *p; + + if (sb->oom) + return -1; + if (need <= sb->cap) + return 0; + + ncap = sb->cap ? sb->cap : 1024; + while (ncap < need) + ncap *= 2; + + p = _realloc(sb->data, ncap); + if (!p) { + sb->oom = 1; + return -1; + } + sb->data = p; + sb->cap = ncap; + return 0; +} + +static void strbuf_appendf(struct strbuf *sb, const char *fmt, ...) + __attribute__((format(gnu_printf, 2, 3))); + +static void strbuf_appendf(struct strbuf *sb, const char *fmt, ...) +{ + va_list ap; + int n; + char *dst; + size_t avail; + + if (sb->oom) + return; + + for (;;) { + /* On the very first append sb->data is still NULL and + * sb->cap is zero. Computing sb->data + sb->len in that + * state would be NULL pointer arithmetic (UB per the C + * standard); pass NULL directly to vsnprintf instead, + * which is well-defined when the size is zero. + */ + avail = sb->cap - sb->len; + dst = sb->data ? sb->data + sb->len : NULL; + + va_start(ap, fmt); + n = vsnprintf(dst, avail, fmt, ap); + va_end(ap); + + if (n < 0) { + sb->oom = 1; + return; + } + if ((size_t)n < avail) { + sb->len += n; + return; + } + if (strbuf_reserve(sb, n + 1) < 0) + return; + } +} + +static void strbuf_free(struct strbuf *sb) +{ + if (sb->data) + _free(sb->data); + sb->data = NULL; + sb->len = sb->cap = 0; +} + +struct accel_stats { + time_t uptime; + unsigned int cpu; + unsigned long rss_bytes; + unsigned long virt_bytes; + struct triton_stat_t core; + struct ap_session_stat sessions; +}; + +static void read_proc_mem(unsigned long *rss, unsigned long *virt) +{ + char path[64]; + unsigned long vmsize = 0, vmrss = 0; + long page_size = sysconf(_SC_PAGESIZE); + FILE *f; + + snprintf(path, sizeof(path), "/proc/%i/statm", getpid()); + f = fopen(path, "r"); + if (f) { + if (fscanf(f, "%lu %lu", &vmsize, &vmrss) != 2) { + vmsize = 0; + vmrss = 0; + } + fclose(f); + } + + *rss = (unsigned long)vmrss * (page_size > 0 ? page_size : 4096); + *virt = (unsigned long)vmsize * (page_size > 0 ? page_size : 4096); +} + +static void gather_stats(struct accel_stats *s) +{ + struct timespec ts; + + memset(s, 0, sizeof(*s)); + triton_stat_get(&s->core); + ap_session_stat_get(&s->sessions); + + clock_gettime(CLOCK_MONOTONIC, &ts); + s->uptime = ts.tv_sec - s->core.start_time; + s->cpu = s->core.cpu; + + read_proc_mem(&s->rss_bytes, &s->virt_bytes); +} + +static void emit_prom_gauge(struct strbuf *sb, const char *name, + const char *help, unsigned long long value) +{ + strbuf_appendf(sb, "# HELP %s %s\n", name, help); + strbuf_appendf(sb, "# TYPE %s gauge\n", name); + strbuf_appendf(sb, "%s %llu\n", name, value); +} + +static const char *session_state_name(int state) +{ + switch (state) { + case AP_STATE_STARTING: + return "starting"; + case AP_STATE_ACTIVE: + return "active"; + case AP_STATE_FINISHING: + return "finishing"; + case AP_STATE_RESTORE: + return "restore"; + default: + return "unknown"; + } +} + +static void append_prefix_len(char *buf, size_t len, int prefix_len) +{ + size_t pos = strlen(buf); + + if (pos < len) + snprintf(buf + pos, len - pos, "/%i", prefix_len); +} + +static void session_ipv6(struct ap_session *ses, char *buf, size_t len, int with_plen) +{ + struct ipv6db_addr_t *a; + struct in6_addr addr; + + buf[0] = 0; + if (!ses->ipv6 || list_empty(&ses->ipv6->addr_list)) + return; + + a = list_first_entry(&ses->ipv6->addr_list, typeof(*a), entry); + if (!a->prefix_len) + return; + build_ip6_addr(a, ses->ipv6->peer_intf_id, &addr); + if (!inet_ntop(AF_INET6, &addr, buf, len)) + return; + if (with_plen) + append_prefix_len(buf, len, a->prefix_len); +} + +static void session_ipv6_dp(struct ap_session *ses, char *buf, size_t len) +{ + struct ipv6db_addr_t *a; + + buf[0] = 0; + if (!ses->ipv6_dp || list_empty(&ses->ipv6_dp->prefix_list)) + return; + + a = list_first_entry(&ses->ipv6_dp->prefix_list, typeof(*a), entry); + if (!inet_ntop(AF_INET6, &a->addr, buf, len)) + return; + append_prefix_len(buf, len, a->prefix_len); +} + +/* Bare peer address, no prefix length: this is the address the peer is + * reachable at, mirroring the "ip" column of "accel-cmd show sessions". */ +static void session_ip(struct ap_session *ses, char *buf, size_t len) +{ + if (ses->ipv4) { + inet_ntop(AF_INET, &ses->ipv4->peer_addr, buf, len); + return; + } + session_ipv6(ses, buf, len, 0); +} + +static unsigned long long session_uptime(struct ap_session *ses, time_t now) +{ + time_t end = ses->stop_time ? ses->stop_time : now; + + return end > ses->start_time ? (unsigned long long)(end - ses->start_time) : 0; +} + +static void render_prometheus(struct strbuf *sb) +{ + struct accel_stats s; + + gather_stats(&s); + + strbuf_appendf(sb, "# HELP accel_ppp_build_info accel-ppp build information\n"); + strbuf_appendf(sb, "# TYPE accel_ppp_build_info gauge\n"); + strbuf_appendf(sb, "accel_ppp_build_info{version=\"%s\"} 1\n", ACCEL_PPP_VERSION); + + emit_prom_gauge(sb, "accel_ppp_uptime_seconds", + "Daemon uptime in seconds", + (unsigned long long)s.uptime); + emit_prom_gauge(sb, "accel_ppp_cpu_percent", + "Daemon CPU usage in percent", + (unsigned long long)s.cpu); + emit_prom_gauge(sb, "accel_ppp_memory_rss_bytes", + "Resident set size of the daemon in bytes", + (unsigned long long)s.rss_bytes); + emit_prom_gauge(sb, "accel_ppp_memory_virt_bytes", + "Virtual memory size of the daemon in bytes", + (unsigned long long)s.virt_bytes); + + emit_prom_gauge(sb, "accel_ppp_core_mempool_allocated_bytes", + "Bytes currently allocated from triton mempools", + (unsigned long long)s.core.mempool_allocated); + emit_prom_gauge(sb, "accel_ppp_core_mempool_available_bytes", + "Bytes currently free in triton mempools", + (unsigned long long)s.core.mempool_available); + emit_prom_gauge(sb, "accel_ppp_core_threads", + "Total number of triton worker threads", + s.core.thread_count); + emit_prom_gauge(sb, "accel_ppp_core_threads_active", + "Number of triton worker threads currently active", + s.core.thread_active); + emit_prom_gauge(sb, "accel_ppp_core_contexts", + "Total number of triton contexts", + s.core.context_count); + emit_prom_gauge(sb, "accel_ppp_core_contexts_sleeping", + "Number of triton contexts currently sleeping", + s.core.context_sleeping); + emit_prom_gauge(sb, "accel_ppp_core_contexts_pending", + "Number of triton contexts waiting to run", + s.core.context_pending); + emit_prom_gauge(sb, "accel_ppp_core_md_handlers", + "Total number of triton md handlers", + s.core.md_handler_count); + emit_prom_gauge(sb, "accel_ppp_core_md_handlers_pending", + "Number of triton md handlers with pending events", + s.core.md_handler_pending); + emit_prom_gauge(sb, "accel_ppp_core_timers", + "Total number of triton timers", + s.core.timer_count); + emit_prom_gauge(sb, "accel_ppp_core_timers_pending", + "Number of triton timers pending fire", + s.core.timer_pending); + + strbuf_appendf(sb, "# HELP accel_ppp_sessions Number of sessions in each state\n"); + strbuf_appendf(sb, "# TYPE accel_ppp_sessions gauge\n"); + strbuf_appendf(sb, "accel_ppp_sessions{state=\"starting\"} %u\n", s.sessions.starting); + strbuf_appendf(sb, "accel_ppp_sessions{state=\"active\"} %u\n", s.sessions.active); + strbuf_appendf(sb, "accel_ppp_sessions{state=\"finishing\"} %u\n", s.sessions.finishing); + + strbuf_appendf(sb, "# HELP accel_ppp_protocol_sessions Sessions per protocol and state\n"); + strbuf_appendf(sb, "# TYPE accel_ppp_protocol_sessions gauge\n"); + for (size_t i = 0; i < sizeof(proto_stats) / sizeof(proto_stats[0]); i++) { + struct proto_stat *p = &proto_stats[i]; + + if (!proto_resolve(p)) + continue; + strbuf_appendf(sb, "accel_ppp_protocol_sessions{protocol=\"%s\",state=\"starting\"} %u\n", + p->module, p->starting()); + strbuf_appendf(sb, "accel_ppp_protocol_sessions{protocol=\"%s\",state=\"active\"} %u\n", + p->module, p->active()); + } +} + +/* Length of the well formed UTF-8 sequence starting at s, 0 if the bytes + * there are not one. Overlong forms, surrogates and out of range code + * points are rejected. */ +static int utf8_seq_len(const unsigned char *s) +{ + unsigned int cp, min; + int n, i; + + if (s[0] < 0x80) + return 1; + + if ((s[0] & 0xe0) == 0xc0) { + n = 2; + min = 0x80; + cp = s[0] & 0x1f; + } else if ((s[0] & 0xf0) == 0xe0) { + n = 3; + min = 0x800; + cp = s[0] & 0x0f; + } else if ((s[0] & 0xf8) == 0xf0) { + n = 4; + min = 0x10000; + cp = s[0] & 0x07; + } else + return 0; + + for (i = 1; i < n; i++) { + if ((s[i] & 0xc0) != 0x80) + return 0; + cp = (cp << 6) | (s[i] & 0x3f); + } + + if (cp < min || cp > 0x10ffff || (cp >= 0xd800 && cp <= 0xdfff)) + return 0; + + return n; +} + +static void append_json_string(struct strbuf *sb, const char *str) +{ + const unsigned char *s = (const unsigned char *)str; + const char *esc; + int n; + + strbuf_appendf(sb, "\""); + while (*s) { + esc = NULL; + switch (*s) { + case '"': + esc = "\\\""; + break; + case '\\': + esc = "\\\\"; + break; + case '\b': + esc = "\\b"; + break; + case '\f': + esc = "\\f"; + break; + case '\n': + esc = "\\n"; + break; + case '\r': + esc = "\\r"; + break; + case '\t': + esc = "\\t"; + break; + } + + if (esc) { + strbuf_appendf(sb, "%s", esc); + s++; + continue; + } + + if (*s < 0x20) { + strbuf_appendf(sb, "\\u%04x", *s); + s++; + continue; + } + + /* Usernames and station ids come from the peer and are not + * validated anywhere, so a single malformed sequence would + * otherwise make the whole document undecodable. */ + n = utf8_seq_len(s); + if (!n) { + strbuf_appendf(sb, "\\ufffd"); + s++; + continue; + } + + strbuf_appendf(sb, "%.*s", n, (const char *)s); + s += n; + } + strbuf_appendf(sb, "\""); +} + +static void emit_json_proto(struct strbuf *sb, const char *name, int *first, + unsigned int starting, unsigned int active) +{ + if (!*first) + strbuf_appendf(sb, ","); + *first = 0; + strbuf_appendf(sb, "\"%s\":{\"starting\":%u,\"active\":%u}", + name, starting, active); +} + +static void append_json_field(struct strbuf *sb, int *first, const char *name, + const char *value) +{ + if (!*first) + strbuf_appendf(sb, ","); + *first = 0; + append_json_string(sb, name); + strbuf_appendf(sb, ":"); + append_json_string(sb, value ? value : ""); +} + +/* TODO: Copy a bounded snapshot of the required fields under ses_lock, then + * serialize it after unlocking. This would shorten lock hold time and make it + * practical to add a response-size limit or pagination for large deployments. + */ +/* The whole list is walked with ses_lock held, so nothing in here may + * block or touch the session. In particular ap_session_read_stats() is + * not used: it issues a synchronous netlink round trip per session, + * which would stall every session setup and teardown for the duration + * of a scrape, it writes back into the session while only the read lock + * is held, and it needs the thread local "net" of the session's + * namespace, which this context does not have. The accounting counters + * last sampled by the session itself are reported instead. */ +static void render_json_sessions(struct strbuf *sb) +{ + struct ap_session *ses; + time_t now = _time(); + char ip[INET6_ADDRSTRLEN]; + char ipv6[INET6_ADDRSTRLEN + 5]; + char ipv6_dp[INET6_ADDRSTRLEN + 5]; + int first = 1; + int f; + + strbuf_appendf(sb, ",\"session_details\":["); + pthread_rwlock_rdlock(&ses_lock); + list_for_each_entry(ses, &ses_list, entry) { + session_ip(ses, ip, sizeof(ip)); + session_ipv6(ses, ipv6, sizeof(ipv6), 1); + session_ipv6_dp(ses, ipv6_dp, sizeof(ipv6_dp)); + + strbuf_appendf(sb, "%s", first ? "{" : ",{"); + first = 0; + f = 1; + append_json_field(sb, &f, "session_id", ses->sessionid); + append_json_field(sb, &f, "ifname", ses->ifname); + append_json_field(sb, &f, "username", ses->username); + append_json_field(sb, &f, "ip", ip); + append_json_field(sb, &f, "ipv6", ipv6); + append_json_field(sb, &f, "delegated_ipv6_prefix", ipv6_dp); + append_json_field(sb, &f, "protocol", ses->ctrl ? ses->ctrl->name : NULL); + append_json_field(sb, &f, "state", session_state_name(ses->state)); + append_json_field(sb, &f, "calling_station_id", ses->ctrl ? ses->ctrl->calling_station_id : NULL); + append_json_field(sb, &f, "called_station_id", ses->ctrl ? ses->ctrl->called_station_id : NULL); + append_json_field(sb, &f, "service_name", ses->ctrl ? ses->ctrl->service_name : NULL); + append_json_field(sb, &f, "inbound_if", ses->ctrl ? ses->ctrl->ifname : NULL); + append_json_field(sb, &f, "compression", ses->comp); + append_json_field(sb, &f, "vrf", ses->vrf_name); + append_json_field(sb, &f, "netns", ses->net ? ses->net->name : NULL); + strbuf_appendf(sb, + ",\"uptime_seconds\":%llu,\"rx_bytes\":%" PRIu64 + ",\"tx_bytes\":%" PRIu64 ",\"rx_packets\":%" PRIu64 + ",\"tx_packets\":%" PRIu64 "}", + session_uptime(ses, now), ses->acct_rx_bytes, + ses->acct_tx_bytes, ses->acct_rx_packets, + ses->acct_tx_packets); + } + pthread_rwlock_unlock(&ses_lock); + strbuf_appendf(sb, "]"); +} + +static void render_json(struct strbuf *sb) +{ + struct accel_stats s; + int first = 1; + + gather_stats(&s); + + strbuf_appendf(sb, "{"); + strbuf_appendf(sb, "\"build\":{\"version\":"); + append_json_string(sb, ACCEL_PPP_VERSION); + strbuf_appendf(sb, "},"); + + strbuf_appendf(sb, "\"uptime_seconds\":%llu,", (unsigned long long)s.uptime); + strbuf_appendf(sb, "\"cpu_percent\":%u,", s.cpu); + strbuf_appendf(sb, "\"memory\":{\"rss_bytes\":%lu,\"virt_bytes\":%lu},", + s.rss_bytes, s.virt_bytes); + + strbuf_appendf(sb, "\"core\":{"); + strbuf_appendf(sb, "\"mempool_allocated_bytes\":%" PRIu64 ",", s.core.mempool_allocated); + strbuf_appendf(sb, "\"mempool_available_bytes\":%" PRIu64 ",", s.core.mempool_available); + strbuf_appendf(sb, "\"threads\":%u,", s.core.thread_count); + strbuf_appendf(sb, "\"threads_active\":%u,", s.core.thread_active); + strbuf_appendf(sb, "\"contexts\":%u,", s.core.context_count); + strbuf_appendf(sb, "\"contexts_sleeping\":%u,", s.core.context_sleeping); + strbuf_appendf(sb, "\"contexts_pending\":%u,", s.core.context_pending); + strbuf_appendf(sb, "\"md_handlers\":%u,", s.core.md_handler_count); + strbuf_appendf(sb, "\"md_handlers_pending\":%u,", s.core.md_handler_pending); + strbuf_appendf(sb, "\"timers\":%u,", s.core.timer_count); + strbuf_appendf(sb, "\"timers_pending\":%u", s.core.timer_pending); + strbuf_appendf(sb, "},"); + + strbuf_appendf(sb, + "\"sessions\":{\"starting\":%u,\"active\":%u,\"finishing\":%u},", + s.sessions.starting, s.sessions.active, s.sessions.finishing); + + strbuf_appendf(sb, "\"protocols\":{"); + for (size_t i = 0; i < sizeof(proto_stats) / sizeof(proto_stats[0]); i++) { + struct proto_stat *p = &proto_stats[i]; + + if (!proto_resolve(p)) + continue; + emit_json_proto(sb, p->module, &first, p->starting(), p->active()); + } + strbuf_appendf(sb, "}"); + if (conf_sessions) + render_json_sessions(sb); + + strbuf_appendf(sb, "}\n"); +} + +/* Try to drain cln->xmit_buf to the socket. Returns 0 if the entire + * response was flushed, 1 if a partial write occurred and MD_MODE_WRITE + * was enabled to finish later, -1 if the connection is broken (caller + * should disconnect). + */ +static int xmit_flush(struct metrics_client_t *cln) +{ + int n; + + while (cln->xmit_pos < cln->xmit_len) { + n = write(cln->hnd.fd, + cln->xmit_buf + cln->xmit_pos, + cln->xmit_len - cln->xmit_pos); + if (n < 0) { + if (errno == EINTR) + continue; + if (errno == EAGAIN || errno == EWOULDBLOCK) { + triton_md_enable_handler(&cln->hnd, MD_MODE_WRITE); + return 1; + } + return -1; + } + cln->xmit_pos += n; + } + return 0; +} + +static int format_header(char *buf, size_t size, int status, const char *reason, + const char *ctype, size_t body_len) +{ + int hlen; + + hlen = snprintf(buf, size, + "HTTP/1.1 %d %s\r\n" + "Server: accel-ppp\r\n" + "Content-Type: %s\r\n" + "Content-Length: %zu\r\n" + "Connection: close\r\n" + "\r\n", + status, reason, ctype, body_len); + if (hlen <= 0 || hlen >= (int)size) + return -1; + + return hlen; +} + +static void start_xmit(struct metrics_client_t *cln) +{ + int rc = xmit_flush(cln); + + if (rc < 0) + cln->disconnect = 1; + else if (rc == 0) + cln->disconnect = 1; /* fully flushed, ready to close */ +} + +static void send_response(struct metrics_client_t *cln, int status, const char *reason, + const char *ctype, const char *body, int body_len) +{ + char header[METRICS_HDR_RESERVE]; + int hlen, total; + + if (cln->xmit_buf) + return; /* response already in flight */ + + hlen = format_header(header, sizeof(header), status, reason, ctype, + body_len > 0 ? (size_t)body_len : 0); + if (hlen < 0) { + cln->disconnect = 1; + return; + } + + total = hlen + (body_len > 0 ? body_len : 0); + cln->xmit_buf = _malloc(total); + if (!cln->xmit_buf) { + cln->disconnect = 1; + return; + } + memcpy(cln->xmit_buf, header, hlen); + if (body_len > 0) + memcpy(cln->xmit_buf + hlen, body, body_len); + cln->xmit_pos = 0; + cln->xmit_len = total; + + start_xmit(cln); +} + +static void send_simple(struct metrics_client_t *cln, int status, const char *reason) +{ + char body[128]; + int len; + + len = snprintf(body, sizeof(body), "%d %s\n", status, reason); + send_response(cln, status, reason, "text/plain; charset=utf-8", body, len); +} + +/* Sends a body rendered into sb and takes ownership of its buffer. The + * first METRICS_HDR_RESERVE bytes of sb are unused padding the header is + * written into, so a body that can be several megabytes with sessions=1 + * is not copied a second time. */ +static void send_rendered(struct metrics_client_t *cln, const char *ctype, + struct strbuf *sb) +{ + char header[METRICS_HDR_RESERVE]; + size_t body_len = sb->len - METRICS_HDR_RESERVE; + int hlen; + + if (cln->xmit_buf) + return; /* response already in flight */ + + hlen = format_header(header, sizeof(header), 200, "OK", ctype, body_len); + if (hlen < 0 || sb->len > INT_MAX) { + send_simple(cln, 500, "Internal Server Error"); + return; + } + + memcpy(sb->data + METRICS_HDR_RESERVE - hlen, header, hlen); + cln->xmit_buf = sb->data; + cln->xmit_pos = METRICS_HDR_RESERVE - hlen; + cln->xmit_len = (int)sb->len; + sb->data = NULL; + sb->len = sb->cap = 0; + + start_xmit(cln); +} + +static void serve_metrics(struct metrics_client_t *cln) +{ + struct strbuf sb = {0}; + + /* Reserve room for the response header in front of the body so it + * can be handed to the client without another copy. */ + if (strbuf_reserve(&sb, METRICS_HDR_RESERVE)) + goto err; + sb.len = METRICS_HDR_RESERVE; + + switch (conf_format) { + case METRICS_FORMAT_PROMETHEUS: + render_prometheus(&sb); + break; + case METRICS_FORMAT_JSON: + render_json(&sb); + break; + } + + if (sb.oom || !sb.data) + goto err; + + send_rendered(cln, content_type(), &sb); + strbuf_free(&sb); + return; + +err: + send_simple(cln, 500, "Internal Server Error"); + strbuf_free(&sb); +} + +static void client_timeout(struct triton_timer_t *t); + +static void disconnect_client(struct metrics_client_t *cln) +{ + if (cln->timer.tpd) + triton_timer_del(&cln->timer); + list_del(&cln->entry); + client_count--; + triton_md_unregister_handler(&cln->hnd, 1); + if (cln->recv_buf) + _free(cln->recv_buf); + if (cln->xmit_buf) + _free(cln->xmit_buf); + _free(cln); +} + +static void client_timeout(struct triton_timer_t *t) +{ + struct metrics_client_t *cln = container_of(t, typeof(*cln), timer); + + disconnect_client(cln); +} + +static void handle_request(struct metrics_client_t *cln) +{ + char *line_end, *space1, *space2; + char *method, *path; + + line_end = strstr(cln->recv_buf, "\r\n"); + if (!line_end) + return; + *line_end = 0; + + method = cln->recv_buf; + space1 = strchr(method, ' '); + if (!space1) { + send_simple(cln, 400, "Bad Request"); + return; + } + *space1 = 0; + path = space1 + 1; + space2 = strchr(path, ' '); + if (space2) + *space2 = 0; + + if (strcmp(method, "GET")) { + send_simple(cln, 405, "Method Not Allowed"); + return; + } + + if (!strcmp(path, "/metrics")) + serve_metrics(cln); + else + send_simple(cln, 404, "Not Found"); +} + +static int cln_read(struct triton_md_handler_t *h) +{ + struct metrics_client_t *cln = container_of(h, typeof(*cln), hnd); + int n; + + while (1) { + if (cln->recv_pos >= METRICS_RECV_BUF_SIZE - 1) { + send_simple(cln, 413, "Request Entity Too Large"); + break; + } + + n = read(h->fd, cln->recv_buf + cln->recv_pos, + METRICS_RECV_BUF_SIZE - 1 - cln->recv_pos); + if (n == 0) { + cln->disconnect = 1; + break; + } + if (n < 0) { + if (errno == EAGAIN) + return 0; + cln->disconnect = 1; + break; + } + + cln->recv_pos += n; + cln->recv_buf[cln->recv_pos] = 0; + + if (strstr(cln->recv_buf, "\r\n\r\n")) { + handle_request(cln); + break; + } + } + + if (cln->disconnect) { + disconnect_client(cln); + return -1; + } + if (cln->xmit_buf) + triton_md_disable_handler(&cln->hnd, MD_MODE_READ); + return 0; +} + +static int cln_write(struct triton_md_handler_t *h) +{ + struct metrics_client_t *cln = container_of(h, typeof(*cln), hnd); + int rc; + + rc = xmit_flush(cln); + if (rc == 1) + return 0; /* still partial — keep MD_MODE_WRITE enabled */ + if (rc < 0) { + disconnect_client(cln); + return -1; + } + triton_md_disable_handler(&cln->hnd, MD_MODE_WRITE); + disconnect_client(cln); + return -1; +} + +static void accept_resume(struct triton_timer_t *t) +{ + triton_timer_del(t); + accept_paused = 0; + triton_md_enable_handler(&serv_hnd, MD_MODE_READ); +} + +static void accept_pause(void) +{ + if (accept_paused) + return; + accept_paused = 1; + triton_md_disable_handler(&serv_hnd, MD_MODE_READ); + accept_resume_timer.expire = accept_resume; + accept_resume_timer.expire_tv.tv_sec = METRICS_ACCEPT_BACKOFF; + triton_timer_add(&serv_ctx, &accept_resume_timer, 0); +} + +static int serv_read(struct triton_md_handler_t *h) +{ + struct sockaddr_in addr; + socklen_t size = sizeof(addr); + int sock; + struct metrics_client_t *cln; + int batch; + + /* Cap the number of accepts handled per dispatch. Without this, an + * overflow burst (peers exceeding max_clients or denied by + * allowed_ips) keeps us in this loop accept()ing and immediately + * closing sockets, never yielding back to the triton dispatcher. + * Since the per-client read-timeout timers share serv_ctx, that + * would delay client_timeout() and let stalled clients live past + * read_timeout. Return after METRICS_ACCEPT_BATCH iterations; if + * the listening fd is still readable triton will dispatch us again + * on the next loop after timers have had a chance to fire. + */ + for (batch = 0; batch < METRICS_ACCEPT_BATCH; batch++) { + sock = accept(h->fd, (struct sockaddr *)&addr, &size); + if (sock < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) + return 0; + if (errno == EINTR || errno == ECONNABORTED) + continue; + /* For persistent resource-exhaustion errors + * (EMFILE, ENFILE, ENOBUFS, ENOMEM) the kernel will + * keep the listening fd readable, so a bare + * `continue` spins the worker. Disable the listener + * briefly and retry via a one-shot timer. + */ + log_error("metrics: accept failed: %s; backing off %ds\n", + strerror(errno), METRICS_ACCEPT_BACKOFF); + accept_pause(); + return 0; + } + + if (!ip_allowed(addr.sin_addr.s_addr)) { + close(sock); + continue; + } + + if (conf_max_clients && client_count >= (unsigned int)conf_max_clients) { + close(sock); + continue; + } + + if (fcntl(sock, F_SETFL, O_NONBLOCK)) { + log_error("metrics: failed to set nonblocking mode: %s\n", strerror(errno)); + close(sock); + continue; + } + + cln = _malloc(sizeof(*cln)); + if (!cln) { + close(sock); + continue; + } + memset(cln, 0, sizeof(*cln)); + cln->addr = addr; + cln->hnd.fd = sock; + cln->hnd.read = cln_read; + cln->hnd.write = cln_write; + cln->recv_buf = _malloc(METRICS_RECV_BUF_SIZE); + if (!cln->recv_buf) { + close(sock); + _free(cln); + continue; + } + + list_add_tail(&cln->entry, &clients); + client_count++; + triton_md_register_handler(&serv_ctx, &cln->hnd); + triton_md_enable_handler(&cln->hnd, MD_MODE_READ); + + if (conf_read_timeout > 0) { + cln->timer.expire = client_timeout; + cln->timer.expire_tv.tv_sec = conf_read_timeout; + triton_timer_add(&serv_ctx, &cln->timer, 0); + } + } + + return 0; +} + +static void serv_close(struct triton_context_t *ctx) +{ + struct metrics_client_t *cln; + + if (accept_resume_timer.tpd) + triton_timer_del(&accept_resume_timer); + + while (!list_empty(&clients)) { + cln = list_entry(clients.next, typeof(*cln), entry); + disconnect_client(cln); + } + + triton_md_unregister_handler(&serv_hnd, 1); + triton_context_unregister(ctx); +} + +static struct triton_context_t serv_ctx = { + .close = serv_close, +}; + +static struct triton_md_handler_t serv_hnd = { + .read = serv_read, +}; + +static int start_server(void) +{ + struct sockaddr_in addr; + int reuse = 1; + + if (parse_listen_address(conf_address, &addr) < 0) { + log_emerg("metrics: invalid listen address '%s'\n", conf_address); + return -1; + } + + serv_hnd.fd = socket(PF_INET, SOCK_STREAM, 0); + if (serv_hnd.fd < 0) { + log_emerg("metrics: failed to create socket: %s\n", strerror(errno)); + return -1; + } + + fcntl(serv_hnd.fd, F_SETFD, fcntl(serv_hnd.fd, F_GETFD) | FD_CLOEXEC); + setsockopt(serv_hnd.fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)); + + if (bind(serv_hnd.fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + log_emerg("metrics: failed to bind %s: %s\n", conf_address, strerror(errno)); + goto err; + } + + if (listen(serv_hnd.fd, 16) < 0) { + log_emerg("metrics: failed to listen on %s: %s\n", conf_address, strerror(errno)); + goto err; + } + + if (fcntl(serv_hnd.fd, F_SETFL, O_NONBLOCK)) { + log_emerg("metrics: failed to set nonblocking mode: %s\n", strerror(errno)); + goto err; + } + + triton_context_register(&serv_ctx, NULL); + triton_context_set_priority(&serv_ctx, 0); + triton_md_register_handler(&serv_ctx, &serv_hnd); + triton_md_enable_handler(&serv_hnd, MD_MODE_READ); + triton_context_wakeup(&serv_ctx); + + serv_running = 1; + return 0; + +err: + close(serv_hnd.fd); + serv_hnd.fd = -1; + return -1; +} + +static void init(void) +{ + /* TODO: Add optional HTTP authentication before exposing session_details; + * allowed_ips limits network reachability but does not identify callers. + */ + if (load_config() < 0) + return; + + if (start_server() < 0) + return; + + log_info2("metrics: listening on %s, format %s\n", + conf_address, + conf_format == METRICS_FORMAT_PROMETHEUS ? "prometheus" : "json"); +} + +DEFINE_INIT(100, init); diff --git a/accel-pppd/extra/net-snmp/statCore.c b/accel-pppd/extra/net-snmp/statCore.c index c2e64689..7e9047da 100644 --- a/accel-pppd/extra/net-snmp/statCore.c +++ b/accel-pppd/extra/net-snmp/statCore.c @@ -49,7 +49,7 @@ handle_statCoreUpTime(netsnmp_mib_handler *handler, struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); - ts.tv_sec -= triton_stat.start_time; + ts.tv_sec -= triton_stat_start_time(); /* We are never called for a GETNEXT if it's registered as a "instance", as it's "magically" handled for us. */ @@ -80,6 +80,8 @@ handle_statCoreCPU(netsnmp_mib_handler *handler, netsnmp_agent_request_info *reqinfo, netsnmp_request_info *requests) { + long cpu; + /* We are never called for a GETNEXT if it's registered as a "instance", as it's "magically" handled for us. */ @@ -89,9 +91,10 @@ handle_statCoreCPU(netsnmp_mib_handler *handler, switch(reqinfo->mode) { case MODE_GET: + cpu = triton_stat_cpu(); snmp_set_var_typed_value(requests->requestvb, ASN_INTEGER, - (u_char *)&triton_stat.cpu /* XXX: a pointer to the scalar's data */, - sizeof(triton_stat.cpu)/* XXX: the length of the data in bytes */); + (u_char *)&cpu /* XXX: a pointer to the scalar's data */, + sizeof(cpu)/* XXX: the length of the data in bytes */); break; diff --git a/accel-pppd/extra/net-snmp/statIPOE.c b/accel-pppd/extra/net-snmp/statIPOE.c index 24f154fd..33430fe6 100644 --- a/accel-pppd/extra/net-snmp/statIPOE.c +++ b/accel-pppd/extra/net-snmp/statIPOE.c @@ -8,18 +8,17 @@ #include <net-snmp/agent/net-snmp-agent-includes.h> #include "triton.h" +#include "accel-pppd/ctrl/ipoe/ipoe.h" #include "statIPOE.h" -/* - * The variables we want to tie the relevant OIDs to. - * The agent will handle all GET and (if applicable) SET requests - * to these variables automatically, changing the values as needed. - */ - -void ipoe_get_stat(unsigned int **, unsigned int **); - -static unsigned int *stat_starting; -static unsigned int *stat_active; +static int handle_statIPOEStarting(netsnmp_mib_handler *handler, + netsnmp_handler_registration *reginfo, + netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *requests); +static int handle_statIPOEActive(netsnmp_mib_handler *handler, + netsnmp_handler_registration *reginfo, + netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *requests); /* * Our initialization routine, called automatically by the agent @@ -28,9 +27,6 @@ static unsigned int *stat_active; void init_statIPOE(void) { - netsnmp_handler_registration *reg; - netsnmp_watcher_info *winfo; - static oid statIPOEStarting_oid[] = { 1,3,6,1,4,1,8072,100,1,6,1 }; static oid statIPOEActive_oid[] = { 1,3,6,1,4,1,8072,100,1,6,2 }; @@ -43,51 +39,75 @@ init_statIPOE(void) if (!triton_module_loaded("ipoe")) return; - ipoe_get_stat(&stat_starting, &stat_active); - - /* - * Register scalar watchers for each of the MIB objects. - * The ASN type and RO/RW status are taken from the MIB definition, - * but can be adjusted if needed. - * - * In most circumstances, the scalar watcher will handle all - * of the necessary processing. But the NULL parameter in the - * netsnmp_create_handler_registration() call can be used to - * supply a user-provided handler if necessary. - * - * This approach can also be used to handle Counter64, string- - * and OID-based watched scalars (although variable-sized writeable - * objects will need some more specialised initialisation). - */ DEBUGMSGTL(("statIPOE", "Initializing statIPOEStarting scalar integer. Default value = %d\n", 0)); - reg = netsnmp_create_handler_registration( - "statIPOEStarting", NULL, + if (netsnmp_register_scalar(netsnmp_create_handler_registration( + "statIPOEStarting", handle_statIPOEStarting, statIPOEStarting_oid, OID_LENGTH(statIPOEStarting_oid), - HANDLER_CAN_RONLY); - winfo = netsnmp_create_watcher_info( - stat_starting, sizeof(*stat_starting), - ASN_INTEGER, WATCHER_FIXED_SIZE); - if (netsnmp_register_watched_scalar( reg, winfo ) < 0 ) { - snmp_log( LOG_ERR, "Failed to register watched statIPOEStarting" ); + HANDLER_CAN_RONLY)) < 0 ) { + snmp_log( LOG_ERR, "Failed to register statIPOEStarting" ); } DEBUGMSGTL(("statIPOE", "Initializing statIPOEActive scalar integer. Default value = %d\n", 0)); - reg = netsnmp_create_handler_registration( - "statIPOEActive", NULL, + if (netsnmp_register_scalar(netsnmp_create_handler_registration( + "statIPOEActive", handle_statIPOEActive, statIPOEActive_oid, OID_LENGTH(statIPOEActive_oid), - HANDLER_CAN_RONLY); - winfo = netsnmp_create_watcher_info( - stat_active, sizeof(*stat_active), - ASN_INTEGER, WATCHER_FIXED_SIZE); - if (netsnmp_register_watched_scalar( reg, winfo ) < 0 ) { - snmp_log( LOG_ERR, "Failed to register watched statIPOEActive" ); + HANDLER_CAN_RONLY)) < 0 ) { + snmp_log( LOG_ERR, "Failed to register statIPOEActive" ); } DEBUGMSGTL(("statIPOE", "Done initalizing statIPOE module\n")); } + +static int handle_statIPOEStarting(netsnmp_mib_handler *handler, + netsnmp_handler_registration *reginfo, + netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *requests) +{ + long stat; + + (void)handler; + (void)reginfo; + + switch (reqinfo->mode) { + case MODE_GET: + stat = ipoe_stat_starting(); + snmp_set_var_typed_value(requests->requestvb, ASN_INTEGER, + (u_char *)&stat, sizeof(stat)); + break; + default: + snmp_log(LOG_ERR, "unknown mode (%d) in handle_statIPOEStarting\n", reqinfo->mode); + return SNMP_ERR_GENERR; + } + + return SNMP_ERR_NOERROR; +} + +static int handle_statIPOEActive(netsnmp_mib_handler *handler, + netsnmp_handler_registration *reginfo, + netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *requests) +{ + long stat; + + (void)handler; + (void)reginfo; + + switch (reqinfo->mode) { + case MODE_GET: + stat = ipoe_stat_active(); + snmp_set_var_typed_value(requests->requestvb, ASN_INTEGER, + (u_char *)&stat, sizeof(stat)); + break; + default: + snmp_log(LOG_ERR, "unknown mode (%d) in handle_statIPOEActive\n", reqinfo->mode); + return SNMP_ERR_GENERR; + } + + return SNMP_ERR_NOERROR; +} diff --git a/accel-pppd/extra/net-snmp/statL2TP.c b/accel-pppd/extra/net-snmp/statL2TP.c index 59998386..36e6f90b 100644 --- a/accel-pppd/extra/net-snmp/statL2TP.c +++ b/accel-pppd/extra/net-snmp/statL2TP.c @@ -8,19 +8,18 @@ #include <net-snmp/agent/net-snmp-agent-includes.h> #include "triton.h" +#include "accel-pppd/ctrl/l2tp/l2tp.h" #include "statL2TP.h" -/* - * The variables we want to tie the relevant OIDs to. - * The agent will handle all GET and (if applicable) SET requests - * to these variables automatically, changing the values as needed. - */ - -void l2tp_get_stat(unsigned int **, unsigned int **); - -static unsigned int *stat_starting; -static unsigned int *stat_active; +static int handle_statL2TPStarting(netsnmp_mib_handler *handler, + netsnmp_handler_registration *reginfo, + netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *requests); +static int handle_statL2TPActive(netsnmp_mib_handler *handler, + netsnmp_handler_registration *reginfo, + netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *requests); /* * Our initialization routine, called automatically by the agent @@ -29,9 +28,6 @@ static unsigned int *stat_active; void init_statL2TP(void) { - netsnmp_handler_registration *reg; - netsnmp_watcher_info *winfo; - static oid statL2TPStarting_oid[] = { 1,3,6,1,4,1,8072,100,1,4,1 }; static oid statL2TPActive_oid[] = { 1,3,6,1,4,1,8072,100,1,4,2 }; @@ -44,51 +40,75 @@ init_statL2TP(void) if (!triton_module_loaded("l2tp")) return; - l2tp_get_stat(&stat_starting, &stat_active); - - /* - * Register scalar watchers for each of the MIB objects. - * The ASN type and RO/RW status are taken from the MIB definition, - * but can be adjusted if needed. - * - * In most circumstances, the scalar watcher will handle all - * of the necessary processing. But the NULL parameter in the - * netsnmp_create_handler_registration() call can be used to - * supply a user-provided handler if necessary. - * - * This approach can also be used to handle Counter64, string- - * and OID-based watched scalars (although variable-sized writeable - * objects will need some more specialised initialisation). - */ DEBUGMSGTL(("statL2TP", "Initializing statL2TPStarting scalar integer. Default value = %d\n", 0)); - reg = netsnmp_create_handler_registration( - "statL2TPStarting", NULL, + if (netsnmp_register_scalar(netsnmp_create_handler_registration( + "statL2TPStarting", handle_statL2TPStarting, statL2TPStarting_oid, OID_LENGTH(statL2TPStarting_oid), - HANDLER_CAN_RONLY); - winfo = netsnmp_create_watcher_info( - stat_starting, sizeof(*stat_starting), - ASN_INTEGER, WATCHER_FIXED_SIZE); - if (netsnmp_register_watched_scalar( reg, winfo ) < 0 ) { - snmp_log( LOG_ERR, "Failed to register watched statL2TPStarting" ); + HANDLER_CAN_RONLY)) < 0 ) { + snmp_log( LOG_ERR, "Failed to register statL2TPStarting" ); } DEBUGMSGTL(("statL2TP", "Initializing statL2TPActive scalar integer. Default value = %d\n", 0)); - reg = netsnmp_create_handler_registration( - "statL2TPActive", NULL, + if (netsnmp_register_scalar(netsnmp_create_handler_registration( + "statL2TPActive", handle_statL2TPActive, statL2TPActive_oid, OID_LENGTH(statL2TPActive_oid), - HANDLER_CAN_RONLY); - winfo = netsnmp_create_watcher_info( - stat_active, sizeof(*stat_active), - ASN_INTEGER, WATCHER_FIXED_SIZE); - if (netsnmp_register_watched_scalar( reg, winfo ) < 0 ) { - snmp_log( LOG_ERR, "Failed to register watched statL2TPActive" ); + HANDLER_CAN_RONLY)) < 0 ) { + snmp_log( LOG_ERR, "Failed to register statL2TPActive" ); } DEBUGMSGTL(("statL2TP", "Done initalizing statL2TP module\n")); } + +static int handle_statL2TPStarting(netsnmp_mib_handler *handler, + netsnmp_handler_registration *reginfo, + netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *requests) +{ + long stat; + + (void)handler; + (void)reginfo; + + switch (reqinfo->mode) { + case MODE_GET: + stat = l2tp_stat_starting(); + snmp_set_var_typed_value(requests->requestvb, ASN_INTEGER, + (u_char *)&stat, sizeof(stat)); + break; + default: + snmp_log(LOG_ERR, "unknown mode (%d) in handle_statL2TPStarting\n", reqinfo->mode); + return SNMP_ERR_GENERR; + } + + return SNMP_ERR_NOERROR; +} + +static int handle_statL2TPActive(netsnmp_mib_handler *handler, + netsnmp_handler_registration *reginfo, + netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *requests) +{ + long stat; + + (void)handler; + (void)reginfo; + + switch (reqinfo->mode) { + case MODE_GET: + stat = l2tp_stat_active(); + snmp_set_var_typed_value(requests->requestvb, ASN_INTEGER, + (u_char *)&stat, sizeof(stat)); + break; + default: + snmp_log(LOG_ERR, "unknown mode (%d) in handle_statL2TPActive\n", reqinfo->mode); + return SNMP_ERR_GENERR; + } + + return SNMP_ERR_NOERROR; +} diff --git a/accel-pppd/extra/net-snmp/statPPP.c b/accel-pppd/extra/net-snmp/statPPP.c index 37e4630c..7dc1f659 100644 --- a/accel-pppd/extra/net-snmp/statPPP.c +++ b/accel-pppd/extra/net-snmp/statPPP.c @@ -6,9 +6,22 @@ #include <net-snmp/net-snmp-config.h> #include <net-snmp/net-snmp-includes.h> #include <net-snmp/agent/net-snmp-agent-includes.h> + +#include "ap_session.h" #include "statPPP.h" -#include "ppp.h" +static int handle_statPPPStarting(netsnmp_mib_handler *handler, + netsnmp_handler_registration *reginfo, + netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *requests); +static int handle_statPPPActive(netsnmp_mib_handler *handler, + netsnmp_handler_registration *reginfo, + netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *requests); +static int handle_statPPPFinishing(netsnmp_mib_handler *handler, + netsnmp_handler_registration *reginfo, + netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *requests); /* * Our initialization routine, called automatically by the agent @@ -17,77 +30,115 @@ void init_statPPP(void) { - netsnmp_handler_registration *reg; - netsnmp_watcher_info *winfo; - static oid statPPPStarting_oid[] = { 1,3,6,1,4,1,8072,100,1,2,1 }; static oid statPPPActive_oid[] = { 1,3,6,1,4,1,8072,100,1,2,2 }; static oid statPPPFinishing_oid[] = { 1,3,6,1,4,1,8072,100,1,2,3 }; - /* - * a debugging statement. Run the agent with -DstatPPP to see - * the output of this debugging statement. - */ DEBUGMSGTL(("statPPP", "Initializing the statPPP module\n")); - - /* - * Register scalar watchers for each of the MIB objects. - * The ASN type and RO/RW status are taken from the MIB definition, - * but can be adjusted if needed. - * - * In most circumstances, the scalar watcher will handle all - * of the necessary processing. But the NULL parameter in the - * netsnmp_create_handler_registration() call can be used to - * supply a user-provided handler if necessary. - * - * This approach can also be used to handle Counter64, string- - * and OID-based watched scalars (although variable-sized writeable - * objects will need some more specialised initialisation). - */ DEBUGMSGTL(("statPPP", "Initializing statPPPStarting scalar integer. Default value = %d\n", 0)); - reg = netsnmp_create_handler_registration( - "statPPPStarting", NULL, + if (netsnmp_register_scalar(netsnmp_create_handler_registration( + "statPPPStarting", handle_statPPPStarting, statPPPStarting_oid, OID_LENGTH(statPPPStarting_oid), - HANDLER_CAN_RONLY); - winfo = netsnmp_create_watcher_info( - &ap_session_stat.starting, sizeof(ap_session_stat.starting), - ASN_INTEGER, WATCHER_FIXED_SIZE); - if (netsnmp_register_watched_scalar( reg, winfo ) < 0 ) { - snmp_log( LOG_ERR, "Failed to register watched statPPPStarting" ); + HANDLER_CAN_RONLY)) < 0 ) { + snmp_log( LOG_ERR, "Failed to register statPPPStarting" ); } DEBUGMSGTL(("statPPP", "Initializing statPPPActive scalar integer. Default value = %d\n", 0)); - reg = netsnmp_create_handler_registration( - "statPPPActive", NULL, + if (netsnmp_register_scalar(netsnmp_create_handler_registration( + "statPPPActive", handle_statPPPActive, statPPPActive_oid, OID_LENGTH(statPPPActive_oid), - HANDLER_CAN_RONLY); - winfo = netsnmp_create_watcher_info( - &ap_session_stat.active, sizeof(ap_session_stat.active), - ASN_INTEGER, WATCHER_FIXED_SIZE); - if (netsnmp_register_watched_scalar( reg, winfo ) < 0 ) { - snmp_log( LOG_ERR, "Failed to register watched statPPPActive" ); + HANDLER_CAN_RONLY)) < 0 ) { + snmp_log( LOG_ERR, "Failed to register statPPPActive" ); } DEBUGMSGTL(("statPPP", "Initializing statPPPFinishing scalar integer. Default value = %d\n", 0)); - reg = netsnmp_create_handler_registration( - "statPPPFinishing", NULL, + if (netsnmp_register_scalar(netsnmp_create_handler_registration( + "statPPPFinishing", handle_statPPPFinishing, statPPPFinishing_oid, OID_LENGTH(statPPPFinishing_oid), - HANDLER_CAN_RONLY); - winfo = netsnmp_create_watcher_info( - &ap_session_stat.finishing, sizeof(ap_session_stat.finishing), - ASN_INTEGER, WATCHER_FIXED_SIZE); - if (netsnmp_register_watched_scalar( reg, winfo ) < 0 ) { - snmp_log( LOG_ERR, "Failed to register watched statPPPFinishing" ); + HANDLER_CAN_RONLY)) < 0 ) { + snmp_log( LOG_ERR, "Failed to register statPPPFinishing" ); } DEBUGMSGTL(("statPPP", "Done initalizing statPPP module\n")); } + +static int handle_statPPPStarting(netsnmp_mib_handler *handler, + netsnmp_handler_registration *reginfo, + netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *requests) +{ + long stat; + + (void)handler; + (void)reginfo; + + switch (reqinfo->mode) { + case MODE_GET: + stat = ap_session_stat_starting(); + snmp_set_var_typed_value(requests->requestvb, ASN_INTEGER, + (u_char *)&stat, sizeof(stat)); + break; + default: + snmp_log(LOG_ERR, "unknown mode (%d) in handle_statPPPStarting\n", reqinfo->mode); + return SNMP_ERR_GENERR; + } + + return SNMP_ERR_NOERROR; +} + +static int handle_statPPPActive(netsnmp_mib_handler *handler, + netsnmp_handler_registration *reginfo, + netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *requests) +{ + long stat; + + (void)handler; + (void)reginfo; + + switch (reqinfo->mode) { + case MODE_GET: + stat = ap_session_stat_active(); + snmp_set_var_typed_value(requests->requestvb, ASN_INTEGER, + (u_char *)&stat, sizeof(stat)); + break; + default: + snmp_log(LOG_ERR, "unknown mode (%d) in handle_statPPPActive\n", reqinfo->mode); + return SNMP_ERR_GENERR; + } + + return SNMP_ERR_NOERROR; +} + +static int handle_statPPPFinishing(netsnmp_mib_handler *handler, + netsnmp_handler_registration *reginfo, + netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *requests) +{ + long stat; + + (void)handler; + (void)reginfo; + + switch (reqinfo->mode) { + case MODE_GET: + stat = ap_session_stat_finishing(); + snmp_set_var_typed_value(requests->requestvb, ASN_INTEGER, + (u_char *)&stat, sizeof(stat)); + break; + default: + snmp_log(LOG_ERR, "unknown mode (%d) in handle_statPPPFinishing\n", reqinfo->mode); + return SNMP_ERR_GENERR; + } + + return SNMP_ERR_NOERROR; +} diff --git a/accel-pppd/extra/net-snmp/statPPPOE.c b/accel-pppd/extra/net-snmp/statPPPOE.c index 6042dc5b..64e61c3c 100644 --- a/accel-pppd/extra/net-snmp/statPPPOE.c +++ b/accel-pppd/extra/net-snmp/statPPPOE.c @@ -10,16 +10,17 @@ #include "triton.h" #include "statPPPOE.h" -/* - * The variables we want to tie the relevant OIDs to. - * The agent will handle all GET and (if applicable) SET requests - * to these variables automatically, changing the values as needed. - */ +unsigned int pppoe_stat_starting(void); +unsigned int pppoe_stat_active(void); -void pppoe_get_stat(unsigned int **, unsigned int **); - -static unsigned int *stat_starting; -static unsigned int *stat_active; +static int handle_statPPPOEStarting(netsnmp_mib_handler *handler, + netsnmp_handler_registration *reginfo, + netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *requests); +static int handle_statPPPOEActive(netsnmp_mib_handler *handler, + netsnmp_handler_registration *reginfo, + netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *requests); /* * Our initialization routine, called automatically by the agent @@ -28,9 +29,6 @@ static unsigned int *stat_active; void init_statPPPOE(void) { - netsnmp_handler_registration *reg; - netsnmp_watcher_info *winfo; - static oid statPPPOEStarting_oid[] = { 1,3,6,1,4,1,8072,100,1,5,1 }; static oid statPPPOEActive_oid[] = { 1,3,6,1,4,1,8072,100,1,5,2 }; @@ -43,51 +41,69 @@ init_statPPPOE(void) if (!triton_module_loaded("pppoe")) return; - pppoe_get_stat(&stat_starting, &stat_active); - - /* - * Register scalar watchers for each of the MIB objects. - * The ASN type and RO/RW status are taken from the MIB definition, - * but can be adjusted if needed. - * - * In most circumstances, the scalar watcher will handle all - * of the necessary processing. But the NULL parameter in the - * netsnmp_create_handler_registration() call can be used to - * supply a user-provided handler if necessary. - * - * This approach can also be used to handle Counter64, string- - * and OID-based watched scalars (although variable-sized writeable - * objects will need some more specialised initialisation). - */ DEBUGMSGTL(("statPPPOE", "Initializing statPPPOEStarting scalar integer. Default value = %d\n", 0)); - reg = netsnmp_create_handler_registration( - "statPPPOEStarting", NULL, + if (netsnmp_register_scalar(netsnmp_create_handler_registration( + "statPPPOEStarting", handle_statPPPOEStarting, statPPPOEStarting_oid, OID_LENGTH(statPPPOEStarting_oid), - HANDLER_CAN_RONLY); - winfo = netsnmp_create_watcher_info( - stat_starting, sizeof(*stat_starting), - ASN_INTEGER, WATCHER_FIXED_SIZE); - if (netsnmp_register_watched_scalar( reg, winfo ) < 0 ) { - snmp_log( LOG_ERR, "Failed to register watched statPPPOEStarting" ); + HANDLER_CAN_RONLY)) < 0 ) { + snmp_log( LOG_ERR, "Failed to register statPPPOEStarting" ); } DEBUGMSGTL(("statPPPOE", "Initializing statPPPOEActive scalar integer. Default value = %d\n", 0)); - reg = netsnmp_create_handler_registration( - "statPPPOEActive", NULL, + if (netsnmp_register_scalar(netsnmp_create_handler_registration( + "statPPPOEActive", handle_statPPPOEActive, statPPPOEActive_oid, OID_LENGTH(statPPPOEActive_oid), - HANDLER_CAN_RONLY); - winfo = netsnmp_create_watcher_info( - stat_active, sizeof(*stat_active), - ASN_INTEGER, WATCHER_FIXED_SIZE); - if (netsnmp_register_watched_scalar( reg, winfo ) < 0 ) { - snmp_log( LOG_ERR, "Failed to register watched statPPPOEActive" ); + HANDLER_CAN_RONLY)) < 0 ) { + snmp_log( LOG_ERR, "Failed to register statPPPOEActive" ); } DEBUGMSGTL(("statPPPOE", - "Done initalizing statPPPOE module\n")); + "Done initalizing statPPPOE module\n")); +} + +static int handle_statPPPOEStarting(netsnmp_mib_handler *handler, + netsnmp_handler_registration *reginfo, + netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *requests) +{ + long stat; + + switch (reqinfo->mode) { + case MODE_GET: + stat = pppoe_stat_starting(); + snmp_set_var_typed_value(requests->requestvb, ASN_INTEGER, + (u_char *)&stat, sizeof(stat)); + break; + default: + snmp_log(LOG_ERR, "unknown mode (%d) in handle_statPPPOEStarting\n", reqinfo->mode); + return SNMP_ERR_GENERR; + } + + return SNMP_ERR_NOERROR; +} + +static int handle_statPPPOEActive(netsnmp_mib_handler *handler, + netsnmp_handler_registration *reginfo, + netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *requests) +{ + long stat; + + switch (reqinfo->mode) { + case MODE_GET: + stat = pppoe_stat_active(); + snmp_set_var_typed_value(requests->requestvb, ASN_INTEGER, + (u_char *)&stat, sizeof(stat)); + break; + default: + snmp_log(LOG_ERR, "unknown mode (%d) in handle_statPPPOEActive\n", reqinfo->mode); + return SNMP_ERR_GENERR; + } + + return SNMP_ERR_NOERROR; } diff --git a/accel-pppd/extra/net-snmp/statPPTP.c b/accel-pppd/extra/net-snmp/statPPTP.c index 48642202..8a633da3 100644 --- a/accel-pppd/extra/net-snmp/statPPTP.c +++ b/accel-pppd/extra/net-snmp/statPPTP.c @@ -6,20 +6,19 @@ #include <net-snmp/net-snmp-config.h> #include <net-snmp/net-snmp-includes.h> #include <net-snmp/agent/net-snmp-agent-includes.h> -#include "statPPTP.h" #include "triton.h" +#include "accel-pppd/ctrl/pptp/pptp.h" +#include "statPPTP.h" -/* - * The variables we want to tie the relevant OIDs to. - * The agent will handle all GET and (if applicable) SET requests - * to these variables automatically, changing the values as needed. - */ - -void pptp_get_stat(unsigned int **, unsigned int **); - -static unsigned int *stat_starting; -static unsigned int *stat_active; +static int handle_statPPTPStarting(netsnmp_mib_handler *handler, + netsnmp_handler_registration *reginfo, + netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *requests); +static int handle_statPPTPActive(netsnmp_mib_handler *handler, + netsnmp_handler_registration *reginfo, + netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *requests); /* * Our initialization routine, called automatically by the agent @@ -28,9 +27,6 @@ static unsigned int *stat_active; void init_statPPTP(void) { - netsnmp_handler_registration *reg; - netsnmp_watcher_info *winfo; - static oid statPPTPStarting_oid[] = { 1,3,6,1,4,1,8072,100,1,3,1 }; static oid statPPTPActive_oid[] = { 1,3,6,1,4,1,8072,100,1,3,2 }; @@ -43,50 +39,75 @@ init_statPPTP(void) if (!triton_module_loaded("pptp")) return; - pptp_get_stat(&stat_starting, &stat_active); - /* - * Register scalar watchers for each of the MIB objects. - * The ASN type and RO/RW status are taken from the MIB definition, - * but can be adjusted if needed. - * - * In most circumstances, the scalar watcher will handle all - * of the necessary processing. But the NULL parameter in the - * netsnmp_create_handler_registration() call can be used to - * supply a user-provided handler if necessary. - * - * This approach can also be used to handle Counter64, string- - * and OID-based watched scalars (although variable-sized writeable - * objects will need some more specialised initialisation). - */ DEBUGMSGTL(("statPPTP", "Initializing statPPTPStarting scalar integer. Default value = %d\n", 0)); - reg = netsnmp_create_handler_registration( - "statPPTPStarting", NULL, + if (netsnmp_register_scalar(netsnmp_create_handler_registration( + "statPPTPStarting", handle_statPPTPStarting, statPPTPStarting_oid, OID_LENGTH(statPPTPStarting_oid), - HANDLER_CAN_RONLY); - winfo = netsnmp_create_watcher_info( - stat_starting, sizeof(*stat_starting), - ASN_INTEGER, WATCHER_FIXED_SIZE); - if (netsnmp_register_watched_scalar( reg, winfo ) < 0 ) { - snmp_log( LOG_ERR, "Failed to register watched statPPTPStarting" ); + HANDLER_CAN_RONLY)) < 0 ) { + snmp_log( LOG_ERR, "Failed to register statPPTPStarting" ); } DEBUGMSGTL(("statPPTP", "Initializing statPPTPActive scalar integer. Default value = %d\n", 0)); - reg = netsnmp_create_handler_registration( - "statPPTPActive", NULL, + if (netsnmp_register_scalar(netsnmp_create_handler_registration( + "statPPTPActive", handle_statPPTPActive, statPPTPActive_oid, OID_LENGTH(statPPTPActive_oid), - HANDLER_CAN_RONLY); - winfo = netsnmp_create_watcher_info( - stat_active, sizeof(*stat_active), - ASN_INTEGER, WATCHER_FIXED_SIZE); - if (netsnmp_register_watched_scalar( reg, winfo ) < 0 ) { - snmp_log( LOG_ERR, "Failed to register watched statPPTPActive" ); + HANDLER_CAN_RONLY)) < 0 ) { + snmp_log( LOG_ERR, "Failed to register statPPTPActive" ); } DEBUGMSGTL(("statPPTP", "Done initalizing statPPTP module\n")); } + +static int handle_statPPTPStarting(netsnmp_mib_handler *handler, + netsnmp_handler_registration *reginfo, + netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *requests) +{ + long stat; + + (void)handler; + (void)reginfo; + + switch (reqinfo->mode) { + case MODE_GET: + stat = pptp_stat_starting(); + snmp_set_var_typed_value(requests->requestvb, ASN_INTEGER, + (u_char *)&stat, sizeof(stat)); + break; + default: + snmp_log(LOG_ERR, "unknown mode (%d) in handle_statPPTPStarting\n", reqinfo->mode); + return SNMP_ERR_GENERR; + } + + return SNMP_ERR_NOERROR; +} + +static int handle_statPPTPActive(netsnmp_mib_handler *handler, + netsnmp_handler_registration *reginfo, + netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *requests) +{ + long stat; + + (void)handler; + (void)reginfo; + + switch (reqinfo->mode) { + case MODE_GET: + stat = pptp_stat_active(); + snmp_set_var_typed_value(requests->requestvb, ASN_INTEGER, + (u_char *)&stat, sizeof(stat)); + break; + default: + snmp_log(LOG_ERR, "unknown mode (%d) in handle_statPPTPActive\n", reqinfo->mode); + return SNMP_ERR_GENERR; + } + + return SNMP_ERR_NOERROR; +} diff --git a/accel-pppd/extra/net-snmp/statSSTP.c b/accel-pppd/extra/net-snmp/statSSTP.c index 06ba6aa1..62948acd 100644 --- a/accel-pppd/extra/net-snmp/statSSTP.c +++ b/accel-pppd/extra/net-snmp/statSSTP.c @@ -8,18 +8,17 @@ #include <net-snmp/agent/net-snmp-agent-includes.h> #include "triton.h" +#include "accel-pppd/ctrl/sstp/sstp.h" #include "statSSTP.h" -/* - * The variables we want to tie the relevant OIDs to. - * The agent will handle all GET and (if applicable) SET requests - * to these variables automatically, changing the values as needed. - */ - -void sstp_get_stat(unsigned int **, unsigned int **); - -static unsigned int *stat_starting; -static unsigned int *stat_active; +static int handle_statSSTPStarting(netsnmp_mib_handler *handler, + netsnmp_handler_registration *reginfo, + netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *requests); +static int handle_statSSTPActive(netsnmp_mib_handler *handler, + netsnmp_handler_registration *reginfo, + netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *requests); /* * Our initialization routine, called automatically by the agent @@ -28,9 +27,6 @@ static unsigned int *stat_active; void init_statSSTP(void) { - netsnmp_handler_registration *reg; - netsnmp_watcher_info *winfo; - static oid statSSTPStarting_oid[] = { 1,3,6,1,4,1,8072,100,1,7,1 }; static oid statSSTPActive_oid[] = { 1,3,6,1,4,1,8072,100,1,7,2 }; @@ -43,51 +39,75 @@ init_statSSTP(void) if (!triton_module_loaded("sstp")) return; - sstp_get_stat(&stat_starting, &stat_active); - - /* - * Register scalar watchers for each of the MIB objects. - * The ASN type and RO/RW status are taken from the MIB definition, - * but can be adjusted if needed. - * - * In most circumstances, the scalar watcher will handle all - * of the necessary processing. But the NULL parameter in the - * netsnmp_create_handler_registration() call can be used to - * supply a user-provided handler if necessary. - * - * This approach can also be used to handle Counter64, string- - * and OID-based watched scalars (although variable-sized writeable - * objects will need some more specialised initialisation). - */ DEBUGMSGTL(("statSSTP", "Initializing statSSTPStarting scalar integer. Default value = %d\n", 0)); - reg = netsnmp_create_handler_registration( - "statSSTPStarting", NULL, + if (netsnmp_register_scalar(netsnmp_create_handler_registration( + "statSSTPStarting", handle_statSSTPStarting, statSSTPStarting_oid, OID_LENGTH(statSSTPStarting_oid), - HANDLER_CAN_RONLY); - winfo = netsnmp_create_watcher_info( - stat_starting, sizeof(*stat_starting), - ASN_INTEGER, WATCHER_FIXED_SIZE); - if (netsnmp_register_watched_scalar( reg, winfo ) < 0 ) { - snmp_log( LOG_ERR, "Failed to register watched statSSTPStarting" ); + HANDLER_CAN_RONLY)) < 0 ) { + snmp_log( LOG_ERR, "Failed to register statSSTPStarting" ); } DEBUGMSGTL(("statSSTP", "Initializing statSSTPActive scalar integer. Default value = %d\n", 0)); - reg = netsnmp_create_handler_registration( - "statSSTPActive", NULL, + if (netsnmp_register_scalar(netsnmp_create_handler_registration( + "statSSTPActive", handle_statSSTPActive, statSSTPActive_oid, OID_LENGTH(statSSTPActive_oid), - HANDLER_CAN_RONLY); - winfo = netsnmp_create_watcher_info( - stat_active, sizeof(*stat_active), - ASN_INTEGER, WATCHER_FIXED_SIZE); - if (netsnmp_register_watched_scalar( reg, winfo ) < 0 ) { - snmp_log( LOG_ERR, "Failed to register watched statSSTPActive" ); + HANDLER_CAN_RONLY)) < 0 ) { + snmp_log( LOG_ERR, "Failed to register statSSTPActive" ); } DEBUGMSGTL(("statSSTP", "Done initalizing statSSTP module\n")); } + +static int handle_statSSTPStarting(netsnmp_mib_handler *handler, + netsnmp_handler_registration *reginfo, + netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *requests) +{ + long stat; + + (void)handler; + (void)reginfo; + + switch (reqinfo->mode) { + case MODE_GET: + stat = sstp_stat_starting(); + snmp_set_var_typed_value(requests->requestvb, ASN_INTEGER, + (u_char *)&stat, sizeof(stat)); + break; + default: + snmp_log(LOG_ERR, "unknown mode (%d) in handle_statSSTPStarting\n", reqinfo->mode); + return SNMP_ERR_GENERR; + } + + return SNMP_ERR_NOERROR; +} + +static int handle_statSSTPActive(netsnmp_mib_handler *handler, + netsnmp_handler_registration *reginfo, + netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *requests) +{ + long stat; + + (void)handler; + (void)reginfo; + + switch (reqinfo->mode) { + case MODE_GET: + stat = sstp_stat_active(); + snmp_set_var_typed_value(requests->requestvb, ASN_INTEGER, + (u_char *)&stat, sizeof(stat)); + break; + default: + snmp_log(LOG_ERR, "unknown mode (%d) in handle_statSSTPActive\n", reqinfo->mode); + return SNMP_ERR_GENERR; + } + + return SNMP_ERR_NOERROR; +} diff --git a/accel-pppd/extra/pppd_compat.c b/accel-pppd/extra/pppd_compat.c index 040e7d90..12363cd4 100644 --- a/accel-pppd/extra/pppd_compat.c +++ b/accel-pppd/extra/pppd_compat.c @@ -518,6 +518,10 @@ static void write_radattr(struct pppd_compat_pd *pd, struct rad_packet_t *pack) int fd, i; in_addr_t addr; char ip_str[50]; + union { + uint64_t ifid; + uint16_t u16[4]; + } ifid_u; if (ses->state == AP_STATE_ACTIVE) { sprintf(fname1, "%s.%s", conf_radattr_prefix, ses->ifname); @@ -572,6 +576,10 @@ static void write_radattr(struct pppd_compat_pd *pd, struct rad_packet_t *pack) inet_ntop(AF_INET6, &attr->val.ipv6addr, ip_str, sizeof(ip_str)); fprintf(f, "%s\n", ip_str); break; + case ATTR_TYPE_IFID: + ifid_u.ifid = attr->val.ifid; + fprintf(f, "%x:%x:%x:%x\n", ntohs(ifid_u.u16[0]), ntohs(ifid_u.u16[1]), ntohs(ifid_u.u16[2]), ntohs(ifid_u.u16[3])); + break; } } fclose(f); diff --git a/accel-pppd/ifcfg.c b/accel-pppd/ifcfg.c index 3750060b..66f5cc6b 100644 --- a/accel-pppd/ifcfg.c +++ b/accel-pppd/ifcfg.c @@ -225,7 +225,7 @@ void __export ap_session_ifdown(struct ap_session *ses) if (ses->ctrl->ppp) { ifr6.ifr6_addr.s6_addr32[0] = htonl(0xfe800000); - *(uint64_t *)(ifr6.ifr6_addr.s6_addr + 8) = ses->ipv6->intf_id; + memcpy(ifr6.ifr6_addr.s6_addr + 8, &ses->ipv6->intf_id, sizeof(ses->ipv6->intf_id)); ifr6.ifr6_prefixlen = 64; net->sock6_ioctl(SIOCDIFADDR, &ifr6); } @@ -234,7 +234,7 @@ void __export ap_session_ifdown(struct ap_session *ses) if (!a->installed) continue; if (a->prefix_len > 64) - ip6route_del(ses->ifindex, &a->addr, a->prefix_len, NULL, 0, 0); + ip6route_del(ses->ifindex, &a->addr, a->prefix_len, NULL, 0, 0, ses->vrf_name); else { struct in6_addr addr; memcpy(addr.s6_addr, &a->addr, 8); @@ -376,4 +376,4 @@ int __export ap_session_vrf(struct ap_session *ses, const char *vrf_name, int le return 0; } -#endif
\ No newline at end of file +#endif diff --git a/accel-pppd/include/ap_session.h b/accel-pppd/include/ap_session.h index 27471859..a0733986 100644 --- a/accel-pppd/include/ap_session.h +++ b/accel-pppd/include/ap_session.h @@ -85,6 +85,12 @@ struct ap_session struct ipv4db_item_t *ipv4; struct ipv6db_item_t *ipv6; struct ipv6db_prefix_t *ipv6_dp; + /* Per session IPv6 DNS servers, NULL when none were assigned and the + globally configured ones ([ipv6-dns]) should be advertised instead. + Only addr_list, and only the addr member of its entries, is + meaningful here. Owned by whoever sets it, currently the radius + module from the DNS-Server-IPv6-Address attribute (RFC 6911) */ + struct ipv6db_item_t *ipv6_dns; char *ipv4_pool_name; char *ipv6_pool_name; char *dpv6_pool_name; @@ -136,10 +142,14 @@ extern int ap_shutdown; extern int sock_fd; extern int sock6_fd; extern int urandom_fd; -extern struct ap_session_stat ap_session_stat; extern int conf_max_sessions; extern int conf_max_starting; +void ap_session_stat_get(struct ap_session_stat *stat); +unsigned int ap_session_stat_starting(void); +unsigned int ap_session_stat_active(void); +unsigned int ap_session_stat_finishing(void); + void ap_session_init(struct ap_session *ses); void ap_session_set_ifindex(struct ap_session *ses); int ap_session_starting(struct ap_session *ses); diff --git a/accel-pppd/include/crypto.h b/accel-pppd/include/crypto.h deleted file mode 120000 index 2f3f63cb..00000000 --- a/accel-pppd/include/crypto.h +++ /dev/null @@ -1 +0,0 @@ -../../crypto/crypto.h
\ No newline at end of file diff --git a/accel-pppd/include/if_pppox.h b/accel-pppd/include/if_pppox.h deleted file mode 100644 index 0532d18f..00000000 --- a/accel-pppd/include/if_pppox.h +++ /dev/null @@ -1,234 +0,0 @@ -/*************************************************************************** - * Linux PPP over X - Generic PPP transport layer sockets - * Linux PPP over Ethernet (PPPoE) Socket Implementation (RFC 2516) - * - * This file supplies definitions required by the PPP over Ethernet driver - * (pppox.c). All version information wrt this file is located in pppox.c - * - * License: - * 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 __LINUX_IF_PPPOX_H -#define __LINUX_IF_PPPOX_H - - -#include <asm/types.h> -#include <asm/byteorder.h> -#include <linux/version.h> -#include <linux/if.h> -#include <linux/if_ether.h> - -#ifdef __KERNEL__ -#include <linux/in.h> -#include <linux/netdevice.h> -#include <linux/ppp_channel.h> -#endif /* __KERNEL__ */ - -/* For user-space programs to pick up these definitions - * which they wouldn't get otherwise without defining __KERNEL__ - */ -#ifndef AF_PPPOX -#define AF_PPPOX 24 -#define PF_PPPOX AF_PPPOX -#endif /* !(AF_PPPOX) */ - -struct pppoe_addr { - __be16 sid; - unsigned char remote[ETH_ALEN]; - char dev[IFNAMSIZ]; -}; - -struct pptp_addr { - __u16 call_id; - struct in_addr sin_addr; -}; -/************************************************************************ - * Protocols supported by AF_PPPOX - */ -#define PX_PROTO_OE 0 /* Currently just PPPoE */ -#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,22) -#define PX_PROTO_PPTP 1 -#define PX_MAX_PROTO 2 -#else -#define PX_PROTO_PPTP 2 -#define PX_MAX_PROTO 3 -#endif - -struct sockaddr_pppox { - sa_family_t sa_family; /* address family, AF_PPPOX */ - unsigned int sa_protocol; /* protocol identifier */ - union{ - struct pppoe_addr pppoe; - struct pptp_addr pptp; - }sa_addr; -}__attribute__ ((packed)); - - -/********************************************************************* - * - * ioctl interface for defining forwarding of connections - * - ********************************************************************/ - -#define PPPOEIOCSFWD _IOW(0xB1 ,0, size_t) -#define PPPOEIOCDFWD _IO(0xB1 ,1) -/*#define PPPOEIOCGFWD _IOWR(0xB1,2, size_t)*/ - -/* Codes to identify message types */ -#define PADI_CODE 0x09 -#define PADO_CODE 0x07 -#define PADR_CODE 0x19 -#define PADS_CODE 0x65 -#define PADT_CODE 0xa7 -struct pppoe_tag { - __u16 tag_type; - __u16 tag_len; - char tag_data[0]; -} __attribute ((packed)); - -/* Tag identifiers */ -#define PTT_EOL __constant_htons(0x0000) -#define PTT_SRV_NAME __constant_htons(0x0101) -#define PTT_AC_NAME __constant_htons(0x0102) -#define PTT_HOST_UNIQ __constant_htons(0x0103) -#define PTT_AC_COOKIE __constant_htons(0x0104) -#define PTT_VENDOR __constant_htons(0x0105) -#define PTT_RELAY_SID __constant_htons(0x0110) -#define PTT_SRV_ERR __constant_htons(0x0201) -#define PTT_SYS_ERR __constant_htons(0x0202) -#define PTT_GEN_ERR __constant_htons(0x0203) - -struct pppoe_hdr { -#if defined(__LITTLE_ENDIAN_BITFIELD) - __u8 ver : 4; - __u8 type : 4; -#elif defined(__BIG_ENDIAN_BITFIELD) - __u8 type : 4; - __u8 ver : 4; -#else -#error "Please fix <asm/byteorder.h>" -#endif - __u8 code; - __u16 sid; - __u16 length; - struct pppoe_tag tag[0]; -} __attribute__ ((packed)); - - -/* Socket options */ -#define PPTP_SO_TIMEOUT 1 - - -#ifdef __KERNEL__ -#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,0) -struct pppoe_opt { - struct net_device *dev; /* device associated with socket*/ - struct pppoe_addr pa; /* what this socket is bound to*/ - struct sockaddr_pppox relay; /* what socket data will be - relayed to (PPPoE relaying) */ -}; -#endif -struct pptp_opt { - struct pptp_addr src_addr; - struct pptp_addr dst_addr; - int timeout; - __u32 ack_sent, ack_recv; - __u32 seq_sent, seq_recv; - int ppp_flags; - int flags; - struct sk_buff_head skb_buf; - #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) - struct tq_struct buf_work; //check bufferd packets work - struct timer_list buf_timer; - #else - #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,20) - struct delayed_work buf_work; //check bufferd packets work - #else - struct work_struct buf_work; //check bufferd packets work - #endif - #endif - struct gre_statistics *stat; - spinlock_t xmit_lock; - spinlock_t rcv_lock; -}; -#define PPTP_FLAG_PAUSE 0 -#define PPTP_FLAG_PROC 1 - -#include <net/sock.h> - -struct pppox_sock { - /* struct sock must be the first member of pppox_sock */ - #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) - struct ppp_channel chan; - struct sock *sk; - #else - struct sock sk; - struct ppp_channel chan; - #endif - struct pppox_sock *next; /* for hash table */ - union { - struct pppoe_opt pppoe; - struct pptp_opt pptp; - } proto; - unsigned short num; -}; -#define pppoe_dev proto.pppoe.dev -#define pppoe_pa proto.pppoe.pa -#define pppoe_relay proto.pppoe.relay - -static inline struct pppox_sock *pppox_sk(struct sock *sk) -{ - #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) - return (struct pppox_sock *)sk->protinfo.pppox; - #else - return (struct pppox_sock *)sk; - #endif -} - -static inline struct sock *sk_pppox(struct pppox_sock *po) -{ - #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) - return po->sk; - #else - return (struct sock *)po; - #endif -} - -struct module; - -struct pppox_proto { - #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) - int (*create)(struct socket *sock); - #else - int (*create)(struct net *net, struct socket *sock); - #endif - int (*ioctl)(struct socket *sock, unsigned int cmd, - unsigned long arg); - #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,15) - struct module *owner; - #endif -}; - -extern int register_pppox_proto(int proto_num, struct pppox_proto *pp); -extern void unregister_pppox_proto(int proto_num); -extern void pppox_unbind_sock(struct sock *sk);/* delete ppp-channel binding */ -extern int pppox_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg); - -/* PPPoX socket states */ -enum { - PPPOX_NONE = 0, /* initial state */ - PPPOX_CONNECTED = 1, /* connection established ==TCP_ESTABLISHED */ - PPPOX_BOUND = 2, /* bound to ppp device */ - PPPOX_RELAY = 4, /* forwarding is enabled */ - PPPOX_ZOMBIE = 8, /* dead, but still bound to ppp device */ - PPPOX_DEAD = 16 /* dead, useless, please clean me up!*/ -}; - -#endif /* __KERNEL__ */ - -#endif /* !(__LINUX_IF_PPPOX_H) */ diff --git a/accel-pppd/include/ipv6_dns.h b/accel-pppd/include/ipv6_dns.h new file mode 100644 index 00000000..b604d006 --- /dev/null +++ b/accel-pppd/include/ipv6_dns.h @@ -0,0 +1,48 @@ +#ifndef __IPV6_DNS_H +#define __IPV6_DNS_H + +#include <netinet/in.h> + +#include "list.h" +#include "ipdb.h" +#include "ap_session.h" + +/* + * Pick the IPv6 DNS servers to advertise to a session. + * + * A session may have been assigned its own servers (currently by the radius + * module, from the DNS-Server-IPv6-Address attribute of RFC 6911); those take + * precedence. Sessions without any fall back to the globally configured ones, + * which is what every session got before per session servers existed. + * + * Up to 'max' addresses are written to 'dns', the number written is returned. + * Callers advertise nothing when that is 0. + */ +static inline int ipv6_dns_get(const struct ap_session *ses, + const struct in6_addr *conf_dns, int conf_dns_count, + struct in6_addr *dns, int max) +{ + struct ipv6db_addr_t *a; + int count = 0; + + if (ses && ses->ipv6_dns) { + list_for_each_entry(a, &ses->ipv6_dns->addr_list, entry) { + if (count == max) + break; + dns[count++] = a->addr; + } + + /* An empty list means "nothing assigned", not "no DNS at all" */ + if (count) + return count; + } + + while (count < conf_dns_count && count < max) { + dns[count] = conf_dns[count]; + count++; + } + + return count; +} + +#endif diff --git a/accel-pppd/ipdb.c b/accel-pppd/ipdb.c index dd8c61c9..264f67b3 100644 --- a/accel-pppd/ipdb.c +++ b/accel-pppd/ipdb.c @@ -75,15 +75,23 @@ void __export ipdb_put_ipv6_prefix(struct ap_session *ses, struct ipv6db_prefix_ void __export build_ip6_addr(struct ipv6db_addr_t *a, uint64_t intf_id, struct in6_addr *addr) { + uint64_t value; + memcpy(addr, &a->addr, sizeof(*addr)); if (a->prefix_len == 128) return; if (a->prefix_len <= 64) - *(uint64_t *)(addr->s6_addr + 8) = intf_id; - else - *(uint64_t *)(addr->s6_addr + 8) |= intf_id & htobe64((1 << (128 - a->prefix_len)) - 1); + memcpy(addr->s6_addr + 8, &intf_id, sizeof(intf_id)); + else { + /* prefix_len 65..127 means a shift of up to 63 bits: a plain + * int literal 1 is undefined behavior for shifts >= 31, so the + * host bits mask must be built from a 64-bit constant */ + memcpy(&value, addr->s6_addr + 8, sizeof(value)); + value |= intf_id & htobe64((UINT64_C(1) << (128 - a->prefix_len)) - 1); + memcpy(addr->s6_addr + 8, &value, sizeof(value)); + } } diff --git a/accel-pppd/ipv6/dhcpv6.c b/accel-pppd/ipv6/dhcpv6.c index 77fc32f7..a0b159cf 100644 --- a/accel-pppd/ipv6/dhcpv6.c +++ b/accel-pppd/ipv6/dhcpv6.c @@ -20,8 +20,10 @@ #include "log.h" #include "ppp.h" #include "ipdb.h" +#include "ipv6_dns.h" #include "events.h" #include "iputils.h" +#include "utils.h" #include "dhcpv6.h" @@ -162,7 +164,7 @@ static void ev_ses_finished(struct ap_session *ses) if (pd->dp_active) { struct ipv6db_addr_t *p; list_for_each_entry(p, &ses->ipv6_dp->prefix_list, entry) - ip6route_del(0, &p->addr, p->prefix_len, NULL, 0, 0); + ip6route_del(0, &p->addr, p->prefix_len, NULL, 0, 0, NULL); } ipdb_put_ipv6_prefix(ses, ses->ipv6_dp); @@ -184,7 +186,7 @@ static void insert_dp_routes(struct ap_session *ses, struct dhcpv6_pd *pd, struc addr = NULL; list_for_each_entry(p, &ses->ipv6_dp->prefix_list, entry) { - if (ip6route_add(ses->ifindex, &p->addr, p->prefix_len, addr, 0, 0)) { + if (ip6route_add(ses->ifindex, &p->addr, p->prefix_len, addr, 0, 0, NULL)) { err = errno; inet_ntop(AF_INET6, &p->addr, str1, sizeof(str1)); if (addr) @@ -220,23 +222,29 @@ static void insert_status(struct dhcpv6_packet *pkt, struct dhcpv6_option *opt, static void insert_oro(struct dhcpv6_packet *reply, struct dhcpv6_option *opt) { struct dhcpv6_option *opt1; - int i, j; - uint16_t *ptr; - struct in6_addr addr, *addr_ptr; - - for (i = ntohs(opt->hdr->len) / 2, ptr = (uint16_t *)opt->hdr->data; i; i--, ptr++) { - if (ntohs(*ptr) == D6_OPTION_DNS_SERVERS) { - if (conf_dns_count) { - opt1 = dhcpv6_option_alloc(reply, D6_OPTION_DNS_SERVERS, conf_dns_count * sizeof(addr)); - for (j = 0, addr_ptr = (struct in6_addr *)opt1->hdr->data; j < conf_dns_count; j++, addr_ptr++) - memcpy(addr_ptr, conf_dns + j, sizeof(addr)); + int i, j, dns_count; + uint8_t *ptr; + uint16_t code; + struct in6_addr addr; + uint8_t *addr_ptr; + struct in6_addr dns[MAX_DNS_COUNT]; + + for (i = ntohs(opt->hdr->len) / 2, ptr = opt->hdr->data; i; i--, ptr += sizeof(code)) { + code = u_read_be16(ptr); + if (code == D6_OPTION_DNS_SERVERS) { + dns_count = ipv6_dns_get(reply->ses, conf_dns, conf_dns_count, + dns, MAX_DNS_COUNT); + if (dns_count) { + opt1 = dhcpv6_option_alloc(reply, D6_OPTION_DNS_SERVERS, dns_count * sizeof(addr)); + for (j = 0, addr_ptr = opt1->hdr->data; j < dns_count; j++, addr_ptr += sizeof(addr)) + memcpy(addr_ptr, dns + j, sizeof(addr)); } - } else if (ntohs(*ptr) == D6_OPTION_DOMAIN_LIST) { + } else if (code == D6_OPTION_DOMAIN_LIST) { if (conf_dnssl_size) { opt1 = dhcpv6_option_alloc(reply, D6_OPTION_DOMAIN_LIST, conf_dnssl_size); memcpy(opt1->hdr->data, conf_dnssl, conf_dnssl_size); } - } else if (ntohs(*ptr) == D6_OPTION_AFTR_NAME) { + } else if (code == D6_OPTION_AFTR_NAME) { if (conf_aftr_gw_size) { opt1 = dhcpv6_option_alloc(reply, D6_OPTION_AFTR_NAME, conf_aftr_gw_size); memcpy(opt1->hdr->data, conf_aftr_gw, conf_aftr_gw_size); @@ -752,6 +760,32 @@ static void dhcpv6_recv_rebind(struct dhcpv6_packet *req) dhcpv6_send_reply2(req, pd, D6_REPLY); } +static void dhcpv6_recv_confirm(struct dhcpv6_packet *req) +{ + struct dhcpv6_pd *pd = req->pd; + + if (!req->clientid) { + log_ppp_error("dhcpv6: no Client-ID option\n"); + return; + } + + if (req->serverid) { + log_ppp_error("dhcpv6: unexcpected Server-ID option\n"); + return; + } + + if (!pd->clientid) + return; + else if (pd->clientid->hdr.len != req->clientid->hdr.len || memcmp(pd->clientid, req->clientid, sizeof(struct dhcpv6_opt_hdr) + ntohs(req->clientid->hdr.len))) { + log_ppp_error("dhcpv6: unmatched Client-ID option\n"); + return; + } + + req->serverid = conf_serverid; + + dhcpv6_send_reply(req, pd, D6_REPLY); +} + static void dhcpv6_recv_release(struct dhcpv6_packet *pkt) { // don't answer @@ -783,6 +817,9 @@ static void dhcpv6_recv_packet(struct dhcpv6_packet *pkt) case D6_REBIND: dhcpv6_recv_rebind(pkt); break; + case D6_CONFIRM: + dhcpv6_recv_confirm(pkt); + break; case D6_RELEASE: dhcpv6_recv_release(pkt); break; @@ -841,13 +878,15 @@ static int dhcpv6_read(struct triton_md_handler_t *h) static void add_aftr_gw(const char *val) { - int n = strlen(val); + int n; const char *ptr; uint8_t *buf; - if (!val) + if (!val || !*val) return; + n = strlen(val); + if (val[n - 1] == '.') n++; else @@ -888,13 +927,15 @@ static void add_aftr_gw(const char *val) static void add_dnssl(const char *val) { - int n = strlen(val); + int n; const char *ptr; uint8_t *buf; - if (!val) + if (!val || !*val) return; + n = strlen(val); + if (val[n - 1] == '.') n++; else diff --git a/accel-pppd/ipv6/dhcpv6_packet.c b/accel-pppd/ipv6/dhcpv6_packet.c index 1890bc11..f1c168b6 100644 --- a/accel-pppd/ipv6/dhcpv6_packet.c +++ b/accel-pppd/ipv6/dhcpv6_packet.c @@ -4,6 +4,7 @@ #include "log.h" #include "memdebug.h" +#include "utils.h" #include "dhcpv6.h" @@ -13,7 +14,8 @@ struct dict_option { int code; const char *name; int recv; - int len; + int min_len; + int nested; void (*print)(struct dhcpv6_option *, void (*)(const char *fmt, ...)); }; @@ -34,30 +36,30 @@ static void print_ia_prefix(struct dhcpv6_option *opt, void (*print)(const char static void print_aftr_gw(struct dhcpv6_option *opt, void (*print)(const char *fmt, ...)); static struct dict_option known_options[] = { - { D6_OPTION_CLIENTID, "Client-ID", 1, 0, print_clientid }, - { D6_OPTION_SERVERID, "Server-ID", 0, 0, print_clientid }, - { D6_OPTION_IA_NA, "IA-NA", 1, sizeof(struct dhcpv6_opt_ia_na), print_ia_na }, - { D6_OPTION_IA_TA, "IA-TA", 1, sizeof(struct dhcpv6_opt_ia_ta), print_ia_ta }, - { D6_OPTION_IAADDR, "IA-Addr", 1, sizeof(struct dhcpv6_opt_ia_addr), print_ia_addr }, - { D6_OPTION_ORO, "Option-Request", 1, 0, print_oro }, - { D6_OPTION_PREFERENCE, "Preference", 0, 0, print_uint8 }, - { D6_OPTION_ELAPSED_TIME, "Elapsed-Time", 1, 0, print_time }, - { D6_OPTION_RELAY_MSG, "Relay-Message", 1, 0 }, - { D6_OPTION_AUTH, "Auth", 1, 0 }, - { D6_OPTION_PREFERENCE, "Server-Unicast", 0, 0, print_ipv6addr }, - { D6_OPTION_STATUS_CODE, "Status", 0, 0, print_status }, - { D6_OPTION_RAPID_COMMIT, "Rapid-Commit", 1, 0 }, - { D6_OPTION_USER_CLASS, "User-Class", 1, 0 }, - { D6_OPTION_VENDOR_CLASS, "Vendor-Class", 1, 0, print_hex_array }, - { D6_OPTION_VENDOR_SPECIFIC, "Vendor-Specific", 1, 0, print_hex_array }, - { D6_OPTION_INTERFACE_ID, "Interface-ID", 1, 0, print_hex_array }, - { D6_OPTION_RECONF_MSG, "Reconfigure", 0, 0, print_reconf }, - { D6_OPTION_RECONF_ACCEPT, "Reconfigure-Accept", 1, 0 }, - { D6_OPTION_DNS_SERVERS, "DNS", 1, 0, print_ipv6addr_array }, - { D6_OPTION_DOMAIN_LIST, "DNSSL", 1, 0, print_dnssl }, - { D6_OPTION_IA_PD, "IA-PD", 1, sizeof(struct dhcpv6_opt_ia_na), print_ia_na }, - { D6_OPTION_IAPREFIX, "IA-Prefix", 1, sizeof(struct dhcpv6_opt_ia_prefix), print_ia_prefix }, - { D6_OPTION_AFTR_NAME, "AFTR-Name", 1, 0, print_aftr_gw }, + { D6_OPTION_CLIENTID, "Client-ID", 1, sizeof(uint16_t), 0, print_clientid }, + { D6_OPTION_SERVERID, "Server-ID", 0, sizeof(uint16_t), 0, print_clientid }, + { D6_OPTION_IA_NA, "IA-NA", 1, sizeof(struct dhcpv6_opt_ia_na) - sizeof(struct dhcpv6_opt_hdr), 1, print_ia_na }, + { D6_OPTION_IA_TA, "IA-TA", 1, sizeof(struct dhcpv6_opt_ia_ta) - sizeof(struct dhcpv6_opt_hdr), 1, print_ia_ta }, + { D6_OPTION_IAADDR, "IA-Addr", 1, sizeof(struct dhcpv6_opt_ia_addr) - sizeof(struct dhcpv6_opt_hdr), 1, print_ia_addr }, + { D6_OPTION_ORO, "Option-Request", 1, 0, 0, print_oro }, + { D6_OPTION_PREFERENCE, "Preference", 0, sizeof(uint8_t), 0, print_uint8 }, + { D6_OPTION_ELAPSED_TIME, "Elapsed-Time", 1, sizeof(uint16_t), 0, print_time }, + { D6_OPTION_RELAY_MSG, "Relay-Message", 1, sizeof(struct dhcpv6_msg_hdr), 0 }, + { D6_OPTION_AUTH, "Auth", 1, 0, 0 }, + { D6_OPTION_UNICAST, "Server-Unicast", 0, sizeof(struct in6_addr), 0, print_ipv6addr }, + { D6_OPTION_STATUS_CODE, "Status", 0, sizeof(uint16_t), 0, print_status }, + { D6_OPTION_RAPID_COMMIT, "Rapid-Commit", 1, 0, 0 }, + { D6_OPTION_USER_CLASS, "User-Class", 1, 0, 0 }, + { D6_OPTION_VENDOR_CLASS, "Vendor-Class", 1, 0, 0, print_hex_array }, + { D6_OPTION_VENDOR_SPECIFIC, "Vendor-Specific", 1, 0, 0, print_hex_array }, + { D6_OPTION_INTERFACE_ID, "Interface-ID", 1, 0, 0, print_hex_array }, + { D6_OPTION_RECONF_MSG, "Reconfigure", 0, sizeof(uint8_t), 0, print_reconf }, + { D6_OPTION_RECONF_ACCEPT, "Reconfigure-Accept", 1, 0, 0 }, + { D6_OPTION_DNS_SERVERS, "DNS", 1, 0, 0, print_ipv6addr_array }, + { D6_OPTION_DOMAIN_LIST, "DNSSL", 1, 0, 0, print_dnssl }, + { D6_OPTION_IA_PD, "IA-PD", 1, sizeof(struct dhcpv6_opt_ia_na) - sizeof(struct dhcpv6_opt_hdr), 1, print_ia_na }, + { D6_OPTION_IAPREFIX, "IA-Prefix", 1, sizeof(struct dhcpv6_opt_ia_prefix) - sizeof(struct dhcpv6_opt_hdr), 1, print_ia_prefix }, + { D6_OPTION_AFTR_NAME, "AFTR-Name", 1, 0, 0, print_aftr_gw }, { 0 } }; @@ -89,9 +91,14 @@ static void *parse_option(void *ptr, void *endptr, struct list_head *opt_list) break; } - if (dopt->len) { + if (ntohs(opth->len) < dopt->min_len) { + log_warn("dhcpv6: invalid packet received\n"); + return NULL; + } + + if (dopt->nested) { endptr = ptr + sizeof(*opth) + ntohs(opth->len); - ptr += dopt->len; + ptr += sizeof(*opth) + dopt->min_len; while (ptr < endptr) { ptr = parse_option(ptr, endptr, &opt->opt_list); if (!ptr) @@ -109,7 +116,8 @@ struct dhcpv6_packet *dhcpv6_packet_parse(const void *buf, size_t size) struct dhcpv6_opt_hdr *opth; struct dhcpv6_relay *rel; struct dhcpv6_relay_hdr *rhdr; - void *ptr, *endptr; + struct dhcpv6_msg_hdr *inner_hdr; + void *ptr, *endptr, *relay_end, *inner_end; if (size < sizeof(struct dhcpv6_msg_hdr)) { if (conf_verbose) @@ -151,22 +159,37 @@ struct dhcpv6_packet *dhcpv6_packet_parse(const void *buf, size_t size) list_add_tail(&rel->entry, &pkt->relay_list); + inner_hdr = NULL; + inner_end = NULL; + relay_end = endptr; ptr = rhdr->data; - while (ptr < endptr) { + while (ptr < relay_end) { opth = ptr; - if (ptr + sizeof(*opth) > endptr || - ptr + sizeof(*opth) + ntohs(opth->len) > endptr) { + if (ptr + sizeof(*opth) > relay_end || + ptr + sizeof(*opth) + ntohs(opth->len) > relay_end) { log_warn("dhcpv6: invalid packet received\n"); goto error; } if (opth->code == htons(D6_OPTION_RELAY_MSG)) { - pkt->hdr = (struct dhcpv6_msg_hdr *)opth->data; - endptr = opth->data + sizeof(*opth) + ntohs(opth->len); + if (inner_hdr || ntohs(opth->len) < sizeof(*inner_hdr)) { + log_warn("dhcpv6: invalid packet received\n"); + goto error; + } + inner_hdr = (struct dhcpv6_msg_hdr *)opth->data; + inner_end = opth->data + ntohs(opth->len); } ptr += sizeof(*opth) + ntohs(opth->len); } + + if (!inner_hdr) { + log_warn("dhcpv6: invalid packet received\n"); + goto error; + } + + pkt->hdr = inner_hdr; + endptr = inner_end; } ptr = pkt->hdr->data; @@ -455,26 +478,28 @@ static void print_ia_addr(struct dhcpv6_option *opt, void (*print)(const char *f static void print_oro(struct dhcpv6_option *opt, void (*print)(const char *fmt, ...)) { - uint16_t *ptr = (uint16_t *)opt->hdr->data; - uint16_t *end_ptr = ptr + ntohs(opt->hdr->len)/2; + uint8_t *ptr = opt->hdr->data; + uint8_t *end_ptr = ptr + ntohs(opt->hdr->len) / 2 * sizeof(uint16_t); struct dict_option *dopt; + uint16_t code; int f = 0; - for (; ptr < end_ptr; ptr++) { + for (; ptr < end_ptr; ptr += sizeof(uint16_t)) { if (f) print(","); else print(" "); + code = u_read_be16(ptr); for (dopt = known_options; dopt->code; dopt++) { - if (ntohs(*ptr) == dopt->code) + if (code == dopt->code) break; } if (dopt->code) print("%s", dopt->name); else - print("%i", ntohs(*ptr)); + print("%i", code); f = 1; } @@ -491,12 +516,18 @@ static void print_hex_array(struct dhcpv6_option *opt, void (*print)(const char static void print_uint8(struct dhcpv6_option *opt, void (*print)(const char *fmt, ...)) { + if (ntohs(opt->hdr->len) < sizeof(uint8_t)) + return; + print(" %i", *(uint8_t *)opt->hdr->data); } static void print_time(struct dhcpv6_option *opt, void (*print)(const char *fmt, ...)) { - print(" %u", *(uint32_t *)opt->hdr->data); + if (ntohs(opt->hdr->len) < sizeof(uint16_t)) + return; + + print(" %u", u_read_be16(opt->hdr->data)); } static void print_ipv6addr(struct dhcpv6_option *opt, void (*print)(const char *fmt, ...)) @@ -513,9 +544,9 @@ static void print_ipv6addr_array(struct dhcpv6_option *opt, void (*print)(const char str[INET6_ADDRSTRLEN]; int i; int f = 0; - struct in6_addr *addr = (struct in6_addr *)opt->hdr->data; + uint8_t *addr = opt->hdr->data; - for (i = ntohs(opt->hdr->len) / sizeof(*addr); i; i--, addr++) { + for (i = ntohs(opt->hdr->len) / sizeof(struct in6_addr); i; i--, addr += sizeof(struct in6_addr)) { inet_ntop(AF_INET6, addr, str, sizeof(str)); print("%c%s", f ? ',' : ' ', str); f = 1; @@ -531,14 +562,20 @@ static void print_status(struct dhcpv6_option *opt, void (*print)(const char *fm "NoAddrsAvail", "NoBindings", "NotOnLink", - "UseMulticast" + "UseMulticast", "NoPrefixAvail" }; + unsigned int code; - if (ntohs(o->code) < 0 || ntohs(o->code) > sizeof(status_name)) - print(" %u", ntohs(o->code)); + if ((unsigned int)ntohs(opt->hdr->len) < sizeof(o->code)) + return; + + code = ntohs(o->code); + + if (code >= sizeof(status_name) / sizeof(status_name[0])) + print(" %u", code); else - print(" %s", status_name[ntohs(o->code)]); + print(" %s", status_name[code]); } static void print_reconf(struct dhcpv6_option *opt, void (*print)(const char *fmt, ...)) @@ -551,21 +588,40 @@ static void print_dnssl(struct dhcpv6_option *opt, void (*print)(const char *fmt } -static void print_aftr_gw(struct dhcpv6_option *opt, void (*print)(const char *fmt, ...)) { +static void print_aftr_gw(struct dhcpv6_option *opt, void (*print)(const char *fmt, ...)) +{ int len = ntohs(opt->hdr->len); int offset = 0; - char domain[255]; + int pos = 0; + int done = 0; + char domain[256]; uint8_t label_len; - memset(domain, 0, 255); + memset(domain, 0, sizeof(domain)); while (offset < len) { - label_len = opt->hdr->data[offset]; - if (label_len == 0) + label_len = opt->hdr->data[offset++]; + if (label_len == 0) { + done = 1; break; - memcpy(&domain[offset], &opt->hdr->data[offset + 1], label_len); + } + + if (label_len > 63 || offset + label_len > len || + pos + label_len + 1 >= sizeof(domain)) { + print(" <invalid>"); + return; + } + + memcpy(&domain[pos], &opt->hdr->data[offset], label_len); offset += label_len; - domain[offset++] = '.'; + pos += label_len; + domain[pos++] = '.'; + } + + if (!done || offset != len) { + print(" <invalid>"); + return; } + print(" %s", domain); } @@ -577,4 +633,3 @@ static void print_ia_prefix(struct dhcpv6_option *opt, void (*print)(const char inet_ntop(AF_INET6, &o->prefix, str, sizeof(str)); print(" %s/%i pref_lifetime=%i valid_lifetime=%i", str, o->prefix_len, ntohl(o->pref_lifetime), ntohl(o->valid_lifetime)); } - diff --git a/accel-pppd/ipv6/ipv6_dns_test.c b/accel-pppd/ipv6/ipv6_dns_test.c new file mode 100644 index 00000000..72b4751c --- /dev/null +++ b/accel-pppd/ipv6/ipv6_dns_test.c @@ -0,0 +1,157 @@ +/* + * Standalone test for the IPv6 DNS server selection shared by the ipv6_nd and + * ipv6_dhcp modules. + * + * Not part of the cmake build. Compile and run from the top of the tree, with + * a configured build directory around for config.h: + * gcc -O2 -Wall -D_GNU_SOURCE -DAP_SESSIONID_LEN=16 \ + * -I accel-pppd/include -I accel-pppd/triton -I build \ + * -o /tmp/ipv6_dns_test accel-pppd/ipv6/ipv6_dns_test.c && /tmp/ipv6_dns_test + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <arpa/inet.h> + +#include "ipv6_dns.h" + +static int failures; +#define CHECK(cond) do { if (!(cond)) { \ + fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); failures++; } } while (0) + +#define MAX_DNS_COUNT 3 /* as in nd.c and dhcpv6.c */ + +static struct in6_addr a6(const char *str) +{ + struct in6_addr addr; + + if (inet_pton(AF_INET6, str, &addr) != 1) { + fprintf(stderr, "bad address %s\n", str); + exit(1); + } + + return addr; +} + +static int is(const struct in6_addr *addr, const char *str) +{ + struct in6_addr expect = a6(str); + + return memcmp(addr, &expect, sizeof(expect)) == 0; +} + +/* A session carrying 'count' DNS servers taken from 'str' */ +static struct ap_session *session_with(const char **str, int count) +{ + struct ap_session *ses = calloc(1, sizeof(*ses)); + struct ipv6db_item_t *item = calloc(1, sizeof(*item)); + int i; + + INIT_LIST_HEAD(&item->addr_list); + for (i = 0; i < count; i++) { + struct ipv6db_addr_t *a = calloc(1, sizeof(*a)); + + a->addr = a6(str[i]); + a->prefix_len = 128; + list_add_tail(&a->entry, &item->addr_list); + } + + ses->ipv6_dns = item; + + return ses; +} + +static void session_free(struct ap_session *ses) +{ + if (ses->ipv6_dns) { + while (!list_empty(&ses->ipv6_dns->addr_list)) { + struct ipv6db_addr_t *a = list_entry(ses->ipv6_dns->addr_list.next, + typeof(*a), entry); + + list_del(&a->entry); + free(a); + } + free(ses->ipv6_dns); + } + + free(ses); +} + +int main(void) +{ + static const char *four[] = { "2001:db8::1", "2001:db8::2", + "2001:db8::3", "2001:db8::4" }; + struct in6_addr conf_dns[MAX_DNS_COUNT]; + struct in6_addr dns[MAX_DNS_COUNT]; + struct ap_session *ses; + int n; + + conf_dns[0] = a6("fc00::53"); + conf_dns[1] = a6("fc00::54"); + + /* No session at all: the configured servers, as before the feature */ + n = ipv6_dns_get(NULL, conf_dns, 2, dns, MAX_DNS_COUNT); + CHECK(n == 2); + CHECK(is(&dns[0], "fc00::53")); + CHECK(is(&dns[1], "fc00::54")); + + /* Session without assigned servers: same fallback */ + ses = calloc(1, sizeof(*ses)); + n = ipv6_dns_get(ses, conf_dns, 2, dns, MAX_DNS_COUNT); + CHECK(n == 2); + CHECK(is(&dns[0], "fc00::53")); + free(ses); + + /* Nothing configured and nothing assigned: advertise nothing */ + n = ipv6_dns_get(NULL, conf_dns, 0, dns, MAX_DNS_COUNT); + CHECK(n == 0); + + /* Assigned servers win over the configured ones */ + ses = session_with(four, 2); + n = ipv6_dns_get(ses, conf_dns, 2, dns, MAX_DNS_COUNT); + CHECK(n == 2); + CHECK(is(&dns[0], "2001:db8::1")); + CHECK(is(&dns[1], "2001:db8::2")); + + /* ... and win even when nothing is configured */ + n = ipv6_dns_get(ses, conf_dns, 0, dns, MAX_DNS_COUNT); + CHECK(n == 2); + CHECK(is(&dns[0], "2001:db8::1")); + + session_free(ses); + + /* An empty assigned list is "nothing assigned", not "no DNS" */ + ses = session_with(four, 0); + n = ipv6_dns_get(ses, conf_dns, 2, dns, MAX_DNS_COUNT); + CHECK(n == 2); + CHECK(is(&dns[0], "fc00::53")); + session_free(ses); + + /* More assigned than fit: keep the first max, never overrun */ + ses = session_with(four, 4); + memset(dns, 0, sizeof(dns)); + n = ipv6_dns_get(ses, conf_dns, 2, dns, MAX_DNS_COUNT); + CHECK(n == MAX_DNS_COUNT); + CHECK(is(&dns[0], "2001:db8::1")); + CHECK(is(&dns[1], "2001:db8::2")); + CHECK(is(&dns[2], "2001:db8::3")); + + /* Same for the configured ones, should they ever exceed max */ + n = ipv6_dns_get(NULL, conf_dns, 99, dns, MAX_DNS_COUNT); + CHECK(n == MAX_DNS_COUNT); + + /* A caller with no room gets nothing rather than a stomped buffer */ + n = ipv6_dns_get(ses, conf_dns, 2, dns, 0); + CHECK(n == 0); + + session_free(ses); + + if (failures) { + fprintf(stderr, "%d failure(s)\n", failures); + return 1; + } + + printf("all tests passed\n"); + + return 0; +} diff --git a/accel-pppd/ipv6/nd.c b/accel-pppd/ipv6/nd.c index 297e4d63..8b22da14 100644 --- a/accel-pppd/ipv6/nd.c +++ b/accel-pppd/ipv6/nd.c @@ -17,6 +17,7 @@ #include "events.h" #include "mempool.h" #include "ipdb.h" +#include "ipv6_dns.h" #include "iputils.h" #include "memdebug.h" @@ -107,7 +108,8 @@ static void ipv6_nd_send_ra(struct ipv6_nd_handler_t *h, struct sockaddr_in6 *ds //struct nd_opt_mtu *mtu; struct ipv6db_addr_t *a; struct in6_addr addr, peer_addr; - int i, prefix_len; + struct in6_addr dns[MAX_DNS_COUNT]; + int i, prefix_len, dns_count; if (!buf) { log_emerg("out of memory\n"); @@ -174,15 +176,17 @@ static void ipv6_nd_send_ra(struct ipv6_nd_handler_t *h, struct sockaddr_in6 *ds rinfo++; }*/ - if (conf_dns_count) { + dns_count = ipv6_dns_get(ses, conf_dns, conf_dns_count, dns, MAX_DNS_COUNT); + + if (dns_count) { rdnssinfo = (struct nd_opt_rdnss_info_local *)pinfo; memset(rdnssinfo, 0, sizeof(*rdnssinfo)); rdnssinfo->nd_opt_rdnssi_type = ND_OPT_RDNSS_INFORMATION; - rdnssinfo->nd_opt_rdnssi_len = 1 + 2 * conf_dns_count; + rdnssinfo->nd_opt_rdnssi_len = 1 + 2 * dns_count; rdnssinfo->nd_opt_rdnssi_lifetime = htonl(conf_rdnss_lifetime); rdnss_addr = (struct in6_addr *)rdnssinfo->nd_opt_rdnssi; - for (i = 0; i < conf_dns_count; i++) { - memcpy(rdnss_addr, &conf_dns[i], sizeof(*rdnss_addr)); + for (i = 0; i < dns_count; i++) { + memcpy(rdnss_addr, &dns[i], sizeof(*rdnss_addr)); rdnss_addr++; } } else @@ -412,13 +416,15 @@ static void ev_ses_finishing(struct ap_session *ses) static void add_dnssl(const char *val) { - int n = strlen(val); + int n; const char *ptr; uint8_t *buf; - if (!val) + if (!val || !*val) return; + n = strlen(val); + if (val[n - 1] == '.') n++; else diff --git a/accel-pppd/libnetlink/genl.c b/accel-pppd/libnetlink/genl.c index 0c758077..8cdfce89 100644 --- a/accel-pppd/libnetlink/genl.c +++ b/accel-pppd/libnetlink/genl.c @@ -82,12 +82,14 @@ int __export genl_resolve_mcg(const char *family, const char *name, int *fam_id) goto out; } - if (!tb[CTRL_ATTR_MCAST_GROUPS]) - goto out; - + /* report the family id even if the group lookup below fails, the + * caller may still have use for it */ if (fam_id) *fam_id = *(uint16_t *)(RTA_DATA(tb[CTRL_ATTR_FAMILY_ID])); + if (!tb[CTRL_ATTR_MCAST_GROUPS]) + goto out; + parse_rtattr_nested(tb2, GENL_MAX_FAM_GRPS, tb[CTRL_ATTR_MCAST_GROUPS]); for (i = 1; i < GENL_MAX_FAM_GRPS; i++) { diff --git a/accel-pppd/libnetlink/iputils.c b/accel-pppd/libnetlink/iputils.c index 23325fcd..a5e3bc51 100644 --- a/accel-pppd/libnetlink/iputils.c +++ b/accel-pppd/libnetlink/iputils.c @@ -11,10 +11,13 @@ #include <errno.h> #include <time.h> #include <sys/uio.h> -//#include <linux/if_link.h> +#ifdef HAVE_VRF +#include <linux/if_link.h> +#endif //#include <linux/if_addr.h> //#include <linux/rtnetlink.h> #include <linux/fib_rules.h> +#include <net/if.h> #include "log.h" @@ -213,7 +216,8 @@ int __export iplink_vlan_add(const char *ifname, int ifindex, int vid) data = NLMSG_TAIL(&req.n); addattr_l(&req.n, 4096, IFLA_INFO_DATA, NULL, 0); - addattr_l(&req.n, 4096, IFLA_VLAN_ID, &vid, 2); + uint16_t vid_16b = (uint16_t)vid; + addattr_l(&req.n, 4096, IFLA_VLAN_ID, &vid_16b, 2); data->rta_len = (void *)NLMSG_TAIL(&req.n) - (void *)data; linkinfo->rta_len = (void *)NLMSG_TAIL(&req.n) - (void *)linkinfo; @@ -457,7 +461,89 @@ int __export ipaddr_del_peer(int ifindex, in_addr_t addr, in_addr_t peer) return r; } -int __export iproute_add(int ifindex, in_addr_t src, in_addr_t dst, in_addr_t gw, int proto, int mask, uint32_t prio) +#ifdef HAVE_VRF +static int ipvrf_get_table(uint32_t *tb_id, const char *vrf_name) +{ + struct iplink_req { + struct nlmsghdr n; + struct ifinfomsg i; + char buf[4096]; + } req; + struct rtattr *tb[IFLA_MAX+1]; + struct rtattr *li[IFLA_INFO_MAX+1]; + struct rtattr *vrf_attr[IFLA_VRF_MAX + 1]; + struct ifinfomsg *ifi; + struct rtnl_handle *rth; + int len; + int r = -1; + *tb_id = RT_TABLE_MAIN; + + if (!vrf_name) + return 0; + + log_ppp_info2("utils: getting route table for %s\n", vrf_name); + + rth = net->rtnl_get(); + if (!rth) + return -1; + + memset(&req, 0, sizeof(req) - 4096); + + req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)); + req.n.nlmsg_flags = NLM_F_REQUEST; + req.n.nlmsg_type = RTM_GETLINK; + req.i.ifi_family = AF_UNSPEC; + + addattr_l(&req.n, 4096, IFLA_IFNAME, vrf_name, strnlen(vrf_name, IFNAMSIZ)); + + r = rtnl_talk(rth, &req.n, 0, 0, &req.n, NULL, NULL, 0); + if (r < 0) { + log_ppp_error("BUG: route table \"%s\" not found\n", vrf_name); + goto out; + } + + ifi = NLMSG_DATA(&req.n); + + len = req.n.nlmsg_len; + + len -= NLMSG_LENGTH(sizeof(*ifi)); + if (len < 0) { + goto error; + } + + parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), len); + + if (!tb[IFLA_LINKINFO]) + goto error; + + parse_rtattr_nested(li, IFLA_INFO_MAX, tb[IFLA_LINKINFO]); + + if (!li[IFLA_INFO_KIND] || !li[IFLA_INFO_DATA]) + goto error; + + if (strncmp(RTA_DATA(li[IFLA_INFO_KIND]), "vrf", 4)) { + log_ppp_error("BUG: link \"%s\" is not VRF\n", vrf_name); + goto error; + } + + parse_rtattr_nested(vrf_attr, IFLA_VRF_MAX, li[IFLA_INFO_DATA]); + if (vrf_attr[IFLA_VRF_TABLE]) + *tb_id = *(uint32_t *)RTA_DATA(vrf_attr[IFLA_VRF_TABLE]); + + if (*tb_id == 0) + log_ppp_error("BUG: VRF %s is missing table id\n", vrf_name); + + r = 0; + goto out; +error: + r = -1; +out: + net->rtnl_put(rth); + return r; +} +#endif + +int __export iproute_add(int ifindex, in_addr_t src, in_addr_t dst, in_addr_t gw, int proto, int mask, uint32_t prio, const char *vrf_name) { struct ipaddr_req { struct nlmsghdr n; @@ -472,16 +558,26 @@ int __export iproute_add(int ifindex, in_addr_t src, in_addr_t dst, in_addr_t gw memset(&req, 0, sizeof(req) - 4096); + uint32_t rt_table = RT_TABLE_MAIN; +#ifdef HAVE_VRF + if (ipvrf_get_table(&rt_table, vrf_name) < 0) { + r = -1; + goto out; + } +#endif + req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE; req.n.nlmsg_type = RTM_NEWROUTE; req.i.rtm_family = AF_INET; - req.i.rtm_table = RT_TABLE_MAIN; + req.i.rtm_table = rt_table; req.i.rtm_scope = gw ? RT_SCOPE_UNIVERSE : RT_SCOPE_LINK; req.i.rtm_protocol = proto; req.i.rtm_type = RTN_UNICAST; req.i.rtm_dst_len = mask; + if (rt_table != RT_TABLE_MAIN) + addattr32(&req.n, sizeof(req), RTA_TABLE, rt_table); if (ifindex) addattr32(&req.n, sizeof(req), RTA_OIF, ifindex); if (src) @@ -495,12 +591,13 @@ int __export iproute_add(int ifindex, in_addr_t src, in_addr_t dst, in_addr_t gw if (rtnl_talk(rth, &req.n, 0, 0, NULL, NULL, NULL, 0) < 0) r = -1; +out: net->rtnl_put(rth); return r; } -int __export iproute_del(int ifindex, in_addr_t src, in_addr_t dst, in_addr_t gw, int proto, int mask, uint32_t prio) +int __export iproute_del(int ifindex, in_addr_t src, in_addr_t dst, in_addr_t gw, int proto, int mask, uint32_t prio, const char *vrf_name) { struct ipaddr_req { struct nlmsghdr n; @@ -515,16 +612,26 @@ int __export iproute_del(int ifindex, in_addr_t src, in_addr_t dst, in_addr_t gw memset(&req, 0, sizeof(req) - 4096); + uint32_t rt_table = RT_TABLE_MAIN; +#ifdef HAVE_VRF + if (ipvrf_get_table(&rt_table, vrf_name) < 0) { + r = -1; + goto out; + } +#endif + req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); req.n.nlmsg_flags = NLM_F_REQUEST; req.n.nlmsg_type = RTM_DELROUTE; req.i.rtm_family = AF_INET; - req.i.rtm_table = RT_TABLE_MAIN; + req.i.rtm_table = rt_table; req.i.rtm_scope = gw ? RT_SCOPE_UNIVERSE : RT_SCOPE_LINK; req.i.rtm_protocol = proto; req.i.rtm_type = RTN_UNICAST; req.i.rtm_dst_len = mask; + if (rt_table != RT_TABLE_MAIN) + addattr32(&req.n, sizeof(req), RTA_TABLE, rt_table); if (ifindex) addattr32(&req.n, sizeof(req), RTA_OIF, ifindex); if (src) @@ -538,12 +645,13 @@ int __export iproute_del(int ifindex, in_addr_t src, in_addr_t dst, in_addr_t gw if (rtnl_talk(rth, &req.n, 0, 0, NULL, NULL, NULL, 0) < 0) r = -1; +out: net->rtnl_put(rth); return r; } -int __export ip6route_add(int ifindex, const struct in6_addr *dst, int pref_len, const struct in6_addr *gw, int proto, uint32_t prio) +int __export ip6route_add(int ifindex, const struct in6_addr *dst, int pref_len, const struct in6_addr *gw, int proto, uint32_t prio, const char *vrf_name) { struct ipaddr_req { struct nlmsghdr n; @@ -558,17 +666,27 @@ int __export ip6route_add(int ifindex, const struct in6_addr *dst, int pref_len, memset(&req, 0, sizeof(req) - 4096); + uint32_t rt_table = RT_TABLE_MAIN; +#ifdef HAVE_VRF + if (ipvrf_get_table(&rt_table, vrf_name) < 0) { + r = -1; + goto out; + } +#endif + req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE; req.n.nlmsg_type = RTM_NEWROUTE; req.i.rtm_family = AF_INET6; - req.i.rtm_table = RT_TABLE_MAIN; + req.i.rtm_table = rt_table; req.i.rtm_scope = RT_SCOPE_UNIVERSE; req.i.rtm_protocol = proto; req.i.rtm_type = RTN_UNICAST; req.i.rtm_dst_len = pref_len; addattr_l(&req.n, sizeof(req), RTA_DST, dst, sizeof(*dst)); + if (rt_table != RT_TABLE_MAIN) + addattr32(&req.n, sizeof(req), RTA_TABLE, rt_table); if (ifindex) addattr32(&req.n, sizeof(req), RTA_OIF, ifindex); if (gw) @@ -579,12 +697,13 @@ int __export ip6route_add(int ifindex, const struct in6_addr *dst, int pref_len, if (rtnl_talk(rth, &req.n, 0, 0, NULL, NULL, NULL, 0) < 0) r = -1; +out: net->rtnl_put(rth); return r; } -int __export ip6route_del(int ifindex, const struct in6_addr *dst, int pref_len, const struct in6_addr *gw, int proto, uint32_t prio) +int __export ip6route_del(int ifindex, const struct in6_addr *dst, int pref_len, const struct in6_addr *gw, int proto, uint32_t prio, const char *vrf_name) { struct ipaddr_req { struct nlmsghdr n; @@ -599,17 +718,27 @@ int __export ip6route_del(int ifindex, const struct in6_addr *dst, int pref_len, memset(&req, 0, sizeof(req) - 4096); + uint32_t rt_table = RT_TABLE_MAIN; +#ifdef HAVE_VRF + if (ipvrf_get_table(&rt_table, vrf_name) < 0) { + r = -1; + goto out; + } +#endif + req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); req.n.nlmsg_flags = NLM_F_REQUEST; req.n.nlmsg_type = RTM_DELROUTE; req.i.rtm_family = AF_INET6; - req.i.rtm_table = RT_TABLE_MAIN; + req.i.rtm_table = rt_table; req.i.rtm_scope = RT_SCOPE_UNIVERSE; req.i.rtm_protocol = proto; req.i.rtm_type = RTN_UNICAST; req.i.rtm_dst_len = pref_len; addattr_l(&req.n, sizeof(req), RTA_DST, dst, sizeof(*dst)); + if (rt_table != RT_TABLE_MAIN) + addattr32(&req.n, sizeof(req), RTA_TABLE, rt_table); if (ifindex) addattr32(&req.n, sizeof(req), RTA_OIF, ifindex); if (gw) @@ -620,6 +749,7 @@ int __export ip6route_del(int ifindex, const struct in6_addr *dst, int pref_len, if (rtnl_talk(rth, &req.n, 0, 0, NULL, NULL, NULL, 0) < 0) r = -1; +out: net->rtnl_put(rth); return r; diff --git a/accel-pppd/libnetlink/iputils.h b/accel-pppd/libnetlink/iputils.h index 9292cea2..86500882 100644 --- a/accel-pppd/libnetlink/iputils.h +++ b/accel-pppd/libnetlink/iputils.h @@ -4,6 +4,7 @@ #include <linux/if_link.h> #include <netinet/in.h> #include <stdint.h> +#include <config.h> typedef int (*iplink_list_func)(int index, int flags, const char *name, int iflink, int vid, void *arg); @@ -20,12 +21,12 @@ int ipaddr_add_peer(int ifindex, in_addr_t addr, in_addr_t peer_addr); int ipaddr_del(int ifindex, in_addr_t addr, int mask); int ipaddr_del_peer(int ifindex, in_addr_t addr, in_addr_t peer); -int iproute_add(int ifindex, in_addr_t src, in_addr_t dst, in_addr_t gw, int proto, int mask, uint32_t prio); -int iproute_del(int ifindex, in_addr_t src, in_addr_t dst, in_addr_t gw, int proto, int mask, uint32_t prio); +int iproute_add(int ifindex, in_addr_t src, in_addr_t dst, in_addr_t gw, int proto, int mask, uint32_t prio, const char *vrf_name); +int iproute_del(int ifindex, in_addr_t src, in_addr_t dst, in_addr_t gw, int proto, int mask, uint32_t prio, const char *vrf_name); in_addr_t iproute_get(in_addr_t dst, in_addr_t *gw); -int ip6route_add(int ifindex, const struct in6_addr *dst, int pref_len, const struct in6_addr *gw, int proto, uint32_t prio); -int ip6route_del(int ifindex, const struct in6_addr *dst, int pref_len, const struct in6_addr *gw, int proto, uint32_t prio); +int ip6route_add(int ifindex, const struct in6_addr *dst, int pref_len, const struct in6_addr *gw, int proto, uint32_t prio, const char *vrf_name); +int ip6route_del(int ifindex, const struct in6_addr *dst, int pref_len, const struct in6_addr *gw, int proto, uint32_t prio, const char *vrf_name); int ip6addr_add(int ifindex, struct in6_addr *addr, int prefix_len); int ip6addr_add_peer(int ifindex, struct in6_addr *addr, struct in6_addr *peer_addr); int ip6addr_del(int ifindex, struct in6_addr *addr, int prefix_len); diff --git a/accel-pppd/log.c b/accel-pppd/log.c index e4a1e6ed..7fac66df 100644 --- a/accel-pppd/log.c +++ b/accel-pppd/log.c @@ -11,15 +11,12 @@ #include "triton/mempool.h" #include "events.h" #include "ppp.h" +#include "utils.h" #include "log.h" #include "memdebug.h" -#ifndef min -#define min(x,y) ((x)<(y)?(x):(y)) -#endif - #define LOG_MSG 0 #define LOG_ERROR 1 #define LOG_WARN 2 diff --git a/accel-pppd/logs/CMakeLists.txt b/accel-pppd/logs/CMakeLists.txt index 4d8a631a..0052e1ae 100644 --- a/accel-pppd/logs/CMakeLists.txt +++ b/accel-pppd/logs/CMakeLists.txt @@ -33,7 +33,15 @@ IF(LOG_SYSLOG) ) ENDIF(LOG_SYSLOG) +# log_pgsql is deprecated and scheduled for removal, the build flag was +# renamed so that nobody keeps building it without noticing IF(LOG_PGSQL) + message(FATAL_ERROR "${Esc}[31mLOG_PGSQL is deprecated.\n The log_pgsql module is scheduled for removal in a future release.\n Build with -DLOG_PGSQL_DEPRECATED=TRUE if you still need it, and please say so at https://github.com/accel-ppp/accel-ppp/issues so that it is kept${Esc}[m") +ENDIF(LOG_PGSQL) + +IF(LOG_PGSQL_DEPRECATED) + message(WARNING "${Esc}[33mBuilding deprecated module log_pgsql, it is scheduled for removal in a future release.\n Please say so at https://github.com/accel-ppp/accel-ppp/issues if you need it kept${Esc}[m") + find_library(PGSQL_LIBRARY pq HINTS ${CMAKE_SOURCE_DIR}/lib/linux/gcc/${BIT}/lib PATHS ${CMAKE_SOURCE_DIR}/lib/linux/gcc/${BIT}/lib @@ -47,5 +55,5 @@ IF(LOG_PGSQL) INSTALL(TARGETS log_pgsql LIBRARY DESTINATION lib${LIB_SUFFIX}/accel-ppp ) -ENDIF(LOG_PGSQL) +ENDIF(LOG_PGSQL_DEPRECATED) diff --git a/accel-pppd/logs/log_file.c b/accel-pppd/logs/log_file.c index 481afa1e..26dd4422 100644 --- a/accel-pppd/logs/log_file.c +++ b/accel-pppd/logs/log_file.c @@ -385,7 +385,12 @@ static void general_reopen(void) { const char *fname = conf_get_opt("log", "log-file"); int old_fd = -1; - int fd = open(fname, O_WRONLY | O_CREAT | O_APPEND | O_CLOEXEC, S_IRUSR | S_IWUSR); + int fd; + + if (!fname) + return; + + fd = open(fname, O_WRONLY | O_CREAT | O_APPEND | O_CLOEXEC, S_IRUSR | S_IWUSR); if (fd < 0) { log_emerg("log_file: open '%s': %s\n", fname, strerror(errno)); return; @@ -422,7 +427,7 @@ static void free_lpd(struct log_file_pd_t *lpd) if (lpd->lf.fd != -1) close(lpd->lf.fd); if (lpd->lf.new_fd != -1) - close(lpd->lf.fd); + close(lpd->lf.new_fd); spin_unlock(&lpd->lf.lock); mempool_free(lpd); } @@ -710,7 +715,8 @@ static void init(void) if (opt && atoi(opt) > 0) conf_copy = 1; - log_register_target(&general_target); + if (log_file) + log_register_target(&general_target); if (conf_per_user_dir) { log_register_target(&per_user_target); diff --git a/accel-pppd/logs/log_pgsql.c b/accel-pppd/logs/log_pgsql.c index e4b5a9e9..6589da0e 100644 --- a/accel-pppd/logs/log_pgsql.c +++ b/accel-pppd/logs/log_pgsql.c @@ -1,3 +1,13 @@ +/* + * DEPRECATED + * + * This module is scheduled for removal in a future release. It is built + * only when the deprecated LOG_PGSQL_DEPRECATED build flag is given. + * If you depend on it, please object at + * https://github.com/accel-ppp/accel-ppp/issues, otherwise it will be + * deleted. + */ + #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -9,6 +19,7 @@ #include "log.h" #include "list.h" #include "ap_session.h" +#include "utils.h" #include "memdebug.h" @@ -57,20 +68,28 @@ static void unpack_msg(struct log_msg_t *msg) static void set_hdr(struct log_msg_t *msg, struct ap_session *ses) { + const char *username = ses && ses->username ? ses->username : ""; + const char *sessionid = ses && ses->username ? ses->sessionid : ""; struct tm tm; + int pos, len, avail; localtime_r(&msg->timestamp.tv_sec, &tm); strftime(msg->hdr->msg, LOG_CHUNK_SIZE, "%Y-%m-%d %H:%M:%S", &tm); - msg->hdr->len = strlen(msg->hdr->msg) + 1; - if (ses && ses->username) { - strcpy(msg->hdr->msg + msg->hdr->len, ses->username); - msg->hdr->len += strlen(ses->username) + 1; - strcpy(msg->hdr->msg + msg->hdr->len, ses->sessionid); - msg->hdr->len += strlen(ses->sessionid) + 1; - } else - memset(msg->hdr->msg + msg->hdr->len, 0, 2); + pos = strlen(msg->hdr->msg) + 1; + + /* username is peer supplied and may be up to 255 bytes long, + * truncate it to what is left of the chunk, keeping one byte + * for the terminator of the sessionid */ + avail = LOG_CHUNK_SIZE - pos - 1; + len = snprintf(msg->hdr->msg + pos, avail, "%s", username); + pos += min(len, avail - 1) + 1; + avail = LOG_CHUNK_SIZE - pos; + len = snprintf(msg->hdr->msg + pos, avail, "%s", sessionid); + pos += min(len, avail - 1) + 1; + + msg->hdr->len = pos; } static void write_next_msg(void) @@ -284,6 +303,9 @@ static void init(void) { char *opt; + log_warn("log_pgsql: this module is deprecated and is scheduled for removal," + " please object at https://github.com/accel-ppp/accel-ppp/issues if you need it\n"); + spinlock_init(&queue_lock); opt = conf_get_opt("log-pgsql", "conninfo"); diff --git a/accel-pppd/logs/log_tcp.c b/accel-pppd/logs/log_tcp.c index e0663d34..ca803a7e 100644 --- a/accel-pppd/logs/log_tcp.c +++ b/accel-pppd/logs/log_tcp.c @@ -163,7 +163,7 @@ static int log_tcp_connect(struct triton_md_handler_t *h) { struct tcp_target_t *t = container_of(h, typeof(*t), hnd); - if (connect(t->hnd.fd, &t->addr, sizeof(t->addr))) { + if (connect(t->hnd.fd, (struct sockaddr*)&t->addr, sizeof(t->addr))) { if (errno == EAGAIN) return 0; if (errno == EINPROGRESS) @@ -216,7 +216,7 @@ static void start_connect(struct tcp_target_t *t) return; } - if (connect(t->hnd.fd, &t->addr, sizeof(t->addr))) { + if (connect(t->hnd.fd, (struct sockaddr*)&t->addr, sizeof(t->addr))) { if (errno != EINPROGRESS) { log_emerg("log-tcp: connect: %s\n", strerror(errno)); close(t->hnd.fd); diff --git a/accel-pppd/main.c b/accel-pppd/main.c index bbc36d95..221be7be 100644 --- a/accel-pppd/main.c +++ b/accel-pppd/main.c @@ -16,9 +16,7 @@ #include <sys/wait.h> #include <sys/resource.h> -#ifdef CRYPTO_OPENSSL #include <openssl/ssl.h> -#endif #include "triton/triton.h" @@ -43,7 +41,8 @@ static int term; static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t cond = PTHREAD_COND_INITIALIZER; -#ifdef CRYPTO_OPENSSL +static volatile sig_atomic_t need_reload; + #if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) static pthread_mutex_t *ssl_lock_cs; @@ -76,16 +75,14 @@ static void ssl_lock_init(void) static void openssl_init(void) { +#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) SSL_library_init(); SSL_load_error_strings(); OpenSSL_add_all_algorithms(); OpenSSL_add_all_digests(); - -#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) ssl_lock_init(); #endif } -#endif static void change_limits(void) { @@ -105,7 +102,7 @@ static void change_limits(void) log_emerg("main: setrlimit: %s\n", strerror(errno)); } -static void config_reload_notify(int r) +static void config_reload_notify(int r, void *arg) { if (!r) triton_event_fire(EV_CONFIG_RELOAD, NULL); @@ -113,7 +110,7 @@ static void config_reload_notify(int r) static void config_reload(int num) { - triton_conf_reload(config_reload_notify); + need_reload = 1; } static void close_all_fd(void) @@ -393,9 +390,7 @@ int main(int _argc, char **_argv) change_limits(); -#ifdef CRYPTO_OPENSSL openssl_init(); -#endif triton_register_init(0, log_version); @@ -444,7 +439,19 @@ int main(int _argc, char **_argv) backup_restore(internal); #endif - sigwait(&set, &sig); + { + struct timespec ts = { .tv_sec = 1 }; + while (1) { + sig = sigtimedwait(&set, NULL, &ts); + if (sig > 0) + break; + if (need_reload) { + need_reload = 0; + if (triton_conf_reload(config_reload_notify, NULL)) + log_warn("main: config reload is already in progress\n"); + } + } + } log_info1("terminate, sig = %i\n", sig); ap_shutdown_soft(shutdown_cb, 1); diff --git a/accel-pppd/memdebug.c b/accel-pppd/memdebug.c index 5353e572..b3488aaa 100644 --- a/accel-pppd/memdebug.c +++ b/accel-pppd/memdebug.c @@ -42,6 +42,19 @@ struct mem_t static LIST_HEAD(mem_list); static spinlock_t mem_list_lock; +static uint64_t get_tail_magic(const struct mem_t *mem) +{ + uint64_t magic; + + memcpy(&magic, mem->data + mem->size, sizeof(magic)); + return magic; +} + +static void set_tail_magic(struct mem_t *mem) +{ + memcpy(mem->data + mem->size, &mem->magic2, sizeof(mem->magic2)); +} + static struct mem_t *_md_malloc(size_t size, const char *fname, int line) { struct mem_t *mem = malloc(sizeof(*mem) + size + 8); @@ -54,7 +67,7 @@ static struct mem_t *_md_malloc(size_t size, const char *fname, int line) mem->size = size; mem->magic1 = MAGIC1; mem->magic2 = (uint64_t)random() * (uint64_t)random(); - *(uint64_t*)(mem->data + size) = mem->magic2; + set_tail_magic(mem); spin_lock(&mem_list_lock); list_add_tail(&mem->entry, &mem_list); @@ -84,7 +97,7 @@ void __export md_free(void *ptr, const char *fname, int line) abort(); } - if (mem->magic2 != *(uint64_t*)(mem->data + mem->size)) { + if (mem->magic2 != get_tail_magic(mem)) { printf("memory corruption:\nmalloc(%zu) at %s:%i\nfree at %s:%i\n", mem->size, mem->fname, mem->line, fname, line); abort(); @@ -113,7 +126,7 @@ void __export *md_realloc(void *ptr, size_t size, const char *fname, int line) abort(); } - if (mem->magic2 != *(uint64_t*)(mem->data + mem->size)) { + if (mem->magic2 != get_tail_magic(mem)) { printf("memory corruption:\nmalloc(%zu) at %s:%i\nfree at %s:%i\n", mem->size, mem->fname, mem->line, fname, line); abort(); @@ -217,7 +230,7 @@ static void siginfo2(int num) spin_lock(&mem_list_lock); list_for_each_entry(mem, &mem_list, entry) { - if (mem->magic1 != MAGIC1 || mem->magic2 != *(uint64_t*)(mem->data + mem->size)) + if (mem->magic1 != MAGIC1 || mem->magic2 != get_tail_magic(mem)) printf("%s:%i %lu\n", mem->fname, mem->line, (long unsigned)mem->size); } spin_unlock(&mem_list_lock); @@ -233,7 +246,7 @@ void __export md_check(void *ptr) if (mem->magic1 != MAGIC1) abort(); - if (mem->magic2 != *(uint64_t*)(mem->data + mem->size)) + if (mem->magic2 != get_tail_magic(mem)) abort(); } diff --git a/accel-pppd/ppp/ccp_mppe.c b/accel-pppd/ppp/ccp_mppe.c index cb41c0da..5042c8a0 100644 --- a/accel-pppd/ppp/ccp_mppe.c +++ b/accel-pppd/ppp/ccp_mppe.c @@ -10,6 +10,7 @@ #include "ppp_ccp.h" #include "log.h" #include "events.h" +#include "utils.h" #include "memdebug.h" @@ -106,7 +107,7 @@ static int setup_mppe_key(int fd, int transmit, uint8_t *key) memset(buf, 0, sizeof(buf)); buf[0] = CI_MPPE; buf[1] = 6; - *(uint32_t*)(buf + 2) = htonl(MPPE_S | MPPE_H); + u_write_be32(buf + 2, MPPE_S | MPPE_H); if (key) memcpy(buf + 6, key, 16); diff --git a/accel-pppd/ppp/ipv6cp_opt_intfid.c b/accel-pppd/ppp/ipv6cp_opt_intfid.c index fff74160..5de88932 100644 --- a/accel-pppd/ppp/ipv6cp_opt_intfid.c +++ b/accel-pppd/ppp/ipv6cp_opt_intfid.c @@ -1,6 +1,7 @@ #include <stdlib.h> #include <stdio.h> #include <unistd.h> +#include <endian.h> #include <fcntl.h> #include <string.h> #include <errno.h> @@ -25,9 +26,16 @@ static int conf_check_exists; static int conf_intf_id = INTF_ID_FIXED; -static uint64_t conf_intf_id_val = 1; +/* Fixed intf-id values are stored in network byte order: the rest of + * the code (build_ip6_addr(), ifcfg.c, nd.c, ...) copies them into the + * low 8 bytes of the IPv6 address as-is, and parse_intfid() already + * produces network byte order. The old host-order defaults (1 and 2) + * therefore yielded ::100:0:0:0 / ::200:0:0:0 instead of ::1 / ::2 on + * little-endian hosts. Defaults are assigned in init() because + * htobe64() is not a constant expression on all libcs (e.g. musl). */ +static uint64_t conf_intf_id_val; static int conf_peer_intf_id = INTF_ID_FIXED; -static uint64_t conf_peer_intf_id_val = 2; +static uint64_t conf_peer_intf_id_val; static int conf_accept_peer_intf_id; static struct ipv6cp_option_t *ipaddr_init(struct ppp_ipv6cp_t *ipv6cp); @@ -276,12 +284,14 @@ static void ipaddr_print(void (*print)(const char *fmt,...), struct ipv6cp_optio { struct ipaddr_option_t *ipaddr_opt = container_of(opt, typeof(*ipaddr_opt), opt); struct ipv6cp_opt64_t *opt64 = (struct ipv6cp_opt64_t *)ptr; - struct in6_addr a; + struct in6_addr a = {}; + uint64_t intf_id; if (ptr) - *(uint64_t *)(a.s6_addr + 8) = opt64->val; + intf_id = opt64->val; else - *(uint64_t *)(a.s6_addr + 8) = ipaddr_opt->ppp->ses.ipv6->intf_id; + intf_id = ipaddr_opt->ppp->ses.ipv6->intf_id; + memcpy(a.s6_addr + 8, &intf_id, sizeof(intf_id)); print("<addr %x:%x:%x:%x>", ntohs(a.s6_addr16[4]), ntohs(a.s6_addr16[5]), ntohs(a.s6_addr16[6]), ntohs(a.s6_addr16[7])); } @@ -354,6 +364,11 @@ static void load_config(void) static void init() { + /* network byte order, so the resulting addresses are ::1 and ::2 + * regardless of host endianness */ + conf_intf_id_val = htobe64(1); + conf_peer_intf_id_val = htobe64(2); + if (sock6_fd < 0) return; @@ -363,4 +378,3 @@ static void init() } DEFINE_INIT(5, init); - diff --git a/accel-pppd/ppp/ppp.c b/accel-pppd/ppp/ppp.c index 8a4cce7c..cd726e17 100644 --- a/accel-pppd/ppp/ppp.c +++ b/accel-pppd/ppp/ppp.c @@ -12,8 +12,6 @@ #include <sys/ioctl.h> #include "linux_ppp.h" -#include "crypto.h" - #include "triton.h" #include "ap_session.h" @@ -59,7 +57,7 @@ static mempool_t uc_pool; static int ppp_chan_read(struct triton_md_handler_t*); static int ppp_unit_read(struct triton_md_handler_t*); -static void init_layers(struct ppp_t *); +static int init_layers(struct ppp_t *); static void _free_layers(struct ppp_t *); static void start_first_layer(struct ppp_t *); static int setup_ppp_mru(struct ppp_t *ppp); @@ -103,7 +101,8 @@ int __export establish_ppp(struct ppp_t *ppp) goto exit_close_chan; } - init_layers(ppp); + if (init_layers(ppp)) + goto exit_close_chan; if (list_empty(&ppp->layers)) { log_ppp_error("no layers to start\n"); goto exit_close_chan; @@ -145,15 +144,13 @@ int __export connect_ppp_channel(struct ppp_t *ppp) return 0; } - if (uc_size) { - pthread_mutex_lock(&uc_lock); - if (!list_empty(&uc_list)) { - uc = list_entry(uc_list.next, typeof(*uc), entry); - list_del(&uc->entry); - --uc_size; - } - pthread_mutex_unlock(&uc_lock); + pthread_mutex_lock(&uc_lock); + if (!list_empty(&uc_list)) { + uc = list_entry(uc_list.next, typeof(*uc), entry); + list_del(&uc->entry); + --uc_size; } + pthread_mutex_unlock(&uc_lock); if (uc) { ppp->unit_fd = uc->fd; @@ -272,10 +269,11 @@ static void destablish_ppp(struct ppp_t *ppp) if (conf_unit_cache) { struct ifreq ifr; + memset(&ifr, 0, sizeof(ifr)); if (ppp->ses.net != def_net) { if (net->move_link(def_net, ppp->ses.ifindex)) { log_ppp_warn("failed to attach to default namespace\n"); - triton_md_unregister_handler(&ppp->unit_hnd, 1); + triton_md_unregister_handler(&ppp->unit_hnd, 0); goto skip; } ppp->ses.net = def_net; @@ -288,7 +286,7 @@ static void destablish_ppp(struct ppp_t *ppp) strncpy(ifr.ifr_name, ppp->ses.ifname, IFNAMSIZ); if (net->sock_ioctl(SIOCSIFNAME, &ifr)) { log_ppp_warn("failed to rename ppp to default name\n"); - triton_md_unregister_handler(&ppp->unit_hnd, 1); + triton_md_unregister_handler(&ppp->unit_hnd, 0); goto skip; } } @@ -301,11 +299,18 @@ static void destablish_ppp(struct ppp_t *ppp) uc->fd = ppp->unit_fd; uc->unit_idx = ppp->ses.unit_idx; } else - triton_md_unregister_handler(&ppp->unit_hnd, 1); + triton_md_unregister_handler(&ppp->unit_hnd, 0); skip: ap_session_finished(&ppp->ses); + /* The unit fd is closed only after session cleanup (ip-down scripts, + * radattr removal) has finished, so the kernel cannot give the same + * unit index (and thus ifname) to a new session while cleanup still + * references the ifname. */ + if (!uc) + close(ppp->unit_fd); + ppp->unit_fd = -1; destroy_ppp_channel(ppp); @@ -673,7 +678,7 @@ void __export ppp_unregister_layer(struct ppp_layer_t *layer) list_del(&layer->entry); } -static void init_layers(struct ppp_t *ppp) +static int init_layers(struct ppp_t *ppp) { struct layer_node_t *n, *n1; struct ppp_layer_t *l; @@ -686,12 +691,18 @@ static void init_layers(struct ppp_t *ppp) list_add_tail(&n1->entry, &ppp->layers); list_for_each_entry(l, &n->items, entry) { d = l->init(ppp); + if (!d) { + log_ppp_error("ppp layer init failed\n"); + _free_layers(ppp); + return -1; + } d->layer = l; d->started = 0; d->node = n1; list_add_tail(&d->entry, &n1->items); } } + return 0; } static void _free_layers(struct ppp_t *ppp) diff --git a/accel-pppd/ppp/ppp_ccp.c b/accel-pppd/ppp/ppp_ccp.c index f9e05e89..30ef97b9 100644 --- a/accel-pppd/ppp/ppp_ccp.c +++ b/accel-pppd/ppp/ppp_ccp.c @@ -13,6 +13,8 @@ #include "ppp.h" #include "ppp_ccp.h" +#include "ppp_ipcp.h" +#include "ppp_ipv6cp.h" #include "memdebug.h" @@ -202,6 +204,9 @@ static void ccp_layer_up(struct ppp_fsm_t *fsm) return; } ppp_layer_started(ccp->ppp, &ccp->ld); + + ipcp_ccp_started(ccp->ppp); + ipv6cp_ccp_started(ccp->ppp); } } @@ -211,9 +216,12 @@ static void ccp_layer_finished(struct ppp_fsm_t *fsm) log_ppp_debug("ccp_layer_finished\n"); - if (!ccp->started) + if (!ccp->started) { ppp_layer_passive(ccp->ppp, &ccp->ld); - else if (!ccp->ppp->ses.terminating) + + ipcp_ccp_started(ccp->ppp); + ipv6cp_ccp_started(ccp->ppp); + } else if (!ccp->ppp->ses.terminating) ap_session_terminate(&ccp->ppp->ses, TERM_USER_ERROR, 0); fsm->fsm_state = FSM_Closed; @@ -387,10 +395,18 @@ static int ccp_recv_conf_req(struct ppp_ccp_t *ccp, uint8_t *data, int size) ccp->ropt_len = size; while (size > 0) { + if (size < sizeof(*hdr)) { + log_ppp_warn("CCP: ConfReq: truncated option header (%i bytes left)\n", size); + return CCP_OPT_FAIL; + } + hdr = (struct ccp_opt_hdr_t *)data; - if (!hdr->len || hdr->len > size) - break; + if (hdr->len < sizeof(*hdr) || hdr->len > size) { + log_ppp_warn("CCP: ConfReq: invalid length %i of option %i (%i bytes left)\n", + hdr->len, hdr->id, size); + return CCP_OPT_FAIL; + } ropt = _malloc(sizeof(*ropt)); memset(ropt, 0, sizeof(*ropt)); @@ -482,10 +498,17 @@ static int ccp_recv_conf_rej(struct ppp_ccp_t *ccp, uint8_t *data, int size) }*/ while (size > 0) { + if (size < sizeof(*hdr)) { + res = -1; + break; + } + hdr = (struct ccp_opt_hdr_t *)data; - if (!hdr->len || hdr->len > size) + if (hdr->len < sizeof(*hdr) || hdr->len > size) { + res = -1; break; + } list_for_each_entry(lopt, &ccp->options, entry) { if (lopt->id == hdr->id) { @@ -523,10 +546,17 @@ static int ccp_recv_conf_nak(struct ppp_ccp_t *ccp, uint8_t *data, int size) }*/ while (size > 0) { + if (size < sizeof(*hdr)) { + res = -1; + break; + } + hdr = (struct ccp_opt_hdr_t *)data; - if (!hdr->len || hdr->len > size) + if (hdr->len < sizeof(*hdr) || hdr->len > size) { + res = -1; break; + } list_for_each_entry(lopt, &ccp->options, entry) { if (lopt->id == hdr->id) { @@ -566,10 +596,17 @@ static int ccp_recv_conf_ack(struct ppp_ccp_t *ccp, uint8_t *data, int size) }*/ while (size > 0) { + if (size < sizeof(*hdr)) { + res = -1; + break; + } + hdr = (struct ccp_opt_hdr_t *)data; - if (!hdr->len || hdr->len > size) + if (hdr->len < sizeof(*hdr) || hdr->len > size) { + res = -1; break; + } list_for_each_entry(lopt, &ccp->options, entry) { if (lopt->id == hdr->id) { @@ -647,7 +684,7 @@ static void ccp_recv(struct ppp_handler_t*h) } hdr = (struct ccp_hdr_t *)ccp->ppp->buf; - if (ntohs(hdr->len) < PPP_HEADERLEN) { + if (ntohs(hdr->len) < PPP_HEADERLEN || ntohs(hdr->len) > ccp->ppp->buf_size - 2) { log_ppp_warn("CCP: short packet received\n"); return; } @@ -695,8 +732,10 @@ static void ccp_recv(struct ppp_handler_t*h) ppp_fsm_recv_conf_ack(&ccp->fsm); break; case CONFNAK: - ccp_recv_conf_nak(ccp, (uint8_t*)(hdr + 1), ntohs(hdr->len) - PPP_HDRLEN); - ppp_fsm_recv_conf_rej(&ccp->fsm); + if (ccp_recv_conf_nak(ccp, (uint8_t*)(hdr + 1), ntohs(hdr->len) - PPP_HDRLEN)) + ap_session_terminate(&ccp->ppp->ses, TERM_USER_ERROR, 0); + else + ppp_fsm_recv_conf_rej(&ccp->fsm); break; case CONFREJ: if (ccp_recv_conf_rej(ccp, (uint8_t*)(hdr + 1),ntohs(hdr->len) - PPP_HDRLEN)) diff --git a/accel-pppd/ppp/ppp_ipcp.c b/accel-pppd/ppp/ppp_ipcp.c index 416fba93..1beccf8c 100644 --- a/accel-pppd/ppp/ppp_ipcp.c +++ b/accel-pppd/ppp/ppp_ipcp.c @@ -173,6 +173,9 @@ void ipcp_layer_free(struct ppp_layer_data_t *ld) if (ipcp->timeout.tpd) triton_timer_del(&ipcp->timeout); + if (ipcp->delay_ack_buf) + _free(ipcp->delay_ack_buf); + _free(ipcp); } @@ -292,7 +295,11 @@ static void send_conf_ack(struct ppp_fsm_t *fsm) struct ipcp_hdr_t *hdr = (struct ipcp_hdr_t*)ipcp->ppp->buf; if (ipcp->delay_ack) { - send_term_ack(fsm); + /* CCP is still negotiating, withhold the ack until it settles */ + if (ipcp->delay_ack_buf) + _free(ipcp->delay_ack_buf); + ipcp->delay_ack_buf = _malloc(ntohs(hdr->len) + 2); + memcpy(ipcp->delay_ack_buf, hdr, ntohs(hdr->len) + 2); return; } @@ -391,10 +398,18 @@ static int ipcp_recv_conf_req(struct ppp_ipcp_t *ipcp, uint8_t *data, int size) ipcp->ropt_len = size; while (size > 0) { + if (size < sizeof(*hdr)) { + log_ppp_warn("IPCP: ConfReq: truncated option header (%i bytes left)\n", size); + return IPCP_OPT_FAIL; + } + hdr = (struct ipcp_opt_hdr_t *)data; - if (!hdr->len || hdr->len > size) - break; + if (hdr->len < sizeof(*hdr) || hdr->len > size) { + log_ppp_warn("IPCP: ConfReq: invalid length %i of option %i (%i bytes left)\n", + hdr->len, hdr->id, size); + return IPCP_OPT_FAIL; + } ropt = _malloc(sizeof(*ropt)); memset(ropt, 0, sizeof(*ropt)); @@ -503,10 +518,17 @@ static int ipcp_recv_conf_rej(struct ppp_ipcp_t *ipcp, uint8_t *data, int size) }*/ while (size > 0) { + if (size < sizeof(*hdr)) { + res = -1; + break; + } + hdr = (struct ipcp_opt_hdr_t *)data; - if (!hdr->len || hdr->len > size) + if (hdr->len < sizeof(*hdr) || hdr->len > size) { + res = -1; break; + } list_for_each_entry(lopt, &ipcp->options, entry) { if (lopt->id == hdr->id) { @@ -544,10 +566,17 @@ static int ipcp_recv_conf_nak(struct ppp_ipcp_t *ipcp, uint8_t *data, int size) }*/ while (size > 0) { + if (size < sizeof(*hdr)) { + res = -1; + break; + } + hdr = (struct ipcp_opt_hdr_t *)data; - if (!hdr->len || hdr->len > size) + if (hdr->len < sizeof(*hdr) || hdr->len > size) { + res = -1; break; + } list_for_each_entry(lopt, &ipcp->options, entry) { if (lopt->id == hdr->id) { @@ -587,10 +616,17 @@ static int ipcp_recv_conf_ack(struct ppp_ipcp_t *ipcp, uint8_t *data, int size) }*/ while (size > 0) { + if (size < sizeof(*hdr)) { + res = -1; + break; + } + hdr = (struct ipcp_opt_hdr_t *)data; - if (!hdr->len || hdr->len > size) + if (hdr->len < sizeof(*hdr) || hdr->len > size) { + res = -1; break; + } list_for_each_entry(lopt, &ipcp->options, entry) { if (lopt->id == hdr->id) { @@ -671,7 +707,7 @@ static void ipcp_recv(struct ppp_handler_t*h) } hdr = (struct ipcp_hdr_t *)ipcp->ppp->buf; - if (ntohs(hdr->len) < PPP_HEADERLEN) { + if (ntohs(hdr->len) < PPP_HEADERLEN || ntohs(hdr->len) > ipcp->ppp->buf_size - 2) { log_ppp_warn("IPCP: short packet received\n"); return; } @@ -725,8 +761,10 @@ static void ipcp_recv(struct ppp_handler_t*h) ppp_fsm_recv_conf_ack(&ipcp->fsm); break; case CONFNAK: - ipcp_recv_conf_nak(ipcp,(uint8_t*)(hdr + 1), ntohs(hdr->len) - PPP_HDRLEN); - ppp_fsm_recv_conf_rej(&ipcp->fsm); + if (ipcp_recv_conf_nak(ipcp,(uint8_t*)(hdr + 1), ntohs(hdr->len) - PPP_HDRLEN)) + ap_session_terminate(&ipcp->ppp->ses, TERM_USER_ERROR, 0); + else + ppp_fsm_recv_conf_rej(&ipcp->fsm); break; case CONFREJ: if (ipcp_recv_conf_rej(ipcp, (uint8_t*)(hdr + 1), ntohs(hdr->len) - PPP_HDRLEN)) @@ -781,6 +819,40 @@ int ipcp_option_register(struct ipcp_option_handler_t *h) return 0; } +void ipcp_ccp_started(struct ppp_t *ppp) +{ + struct ppp_layer_data_t *ld = ppp_find_layer_data(ppp, &ipcp_layer); + struct ppp_ipcp_t *ipcp; + struct ipcp_hdr_t *hdr; + + if (!ld) + return; + + ipcp = container_of(ld, typeof(*ipcp), ld); + + if (!ipcp->delay_ack) + return; + + ipcp->delay_ack = 0; + + if (ipcp->fsm.fsm_state == FSM_Opened) + __ipcp_layer_up(ipcp); + + if (!ipcp->delay_ack_buf) + return; + + hdr = (struct ipcp_hdr_t *)ipcp->delay_ack_buf; + hdr->code = CONFACK; + + if (conf_ppp_verbose) + log_ppp_info2("send [IPCP ConfAck id=%x]\n", hdr->id); + + ppp_unit_send(ipcp->ppp, hdr, ntohs(hdr->len) + 2); + + _free(ipcp->delay_ack_buf); + ipcp->delay_ack_buf = NULL; +} + struct ipcp_option_t *ipcp_find_option(struct ppp_t *ppp, struct ipcp_option_handler_t *h) { struct ppp_ipcp_t *ipcp = container_of(ppp_find_layer_data(ppp, &ipcp_layer), typeof(*ipcp), ld); diff --git a/accel-pppd/ppp/ppp_ipcp.h b/accel-pppd/ppp/ppp_ipcp.h index 036f0a4d..8cd7790f 100644 --- a/accel-pppd/ppp/ppp_ipcp.h +++ b/accel-pppd/ppp/ppp_ipcp.h @@ -92,12 +92,15 @@ struct ppp_ipcp_t struct list_head ropt_list; // last received ConfReq int ropt_len; + void *delay_ack_buf; // ConfAck withheld until CCP finishes + int conf_req_len; unsigned int starting:1; unsigned int started:1; unsigned int delay_ack:1; }; +void ipcp_ccp_started(struct ppp_t *ppp); int ipcp_option_register(struct ipcp_option_handler_t *h); struct ipcp_option_t *ipcp_find_option(struct ppp_t *ppp, struct ipcp_option_handler_t *h); diff --git a/accel-pppd/ppp/ppp_ipv6cp.c b/accel-pppd/ppp/ppp_ipv6cp.c index 1194b314..3caa89a2 100644 --- a/accel-pppd/ppp/ppp_ipv6cp.c +++ b/accel-pppd/ppp/ppp_ipv6cp.c @@ -173,6 +173,9 @@ void ipv6cp_layer_free(struct ppp_layer_data_t *ld) if (ipv6cp->timeout.tpd) triton_timer_del(&ipv6cp->timeout); + if (ipv6cp->delay_ack_buf) + _free(ipv6cp->delay_ack_buf); + _free(ipv6cp); } @@ -205,8 +208,12 @@ static void ipv6cp_layer_finished(struct ppp_fsm_t *fsm) ap_session_terminate(&ipv6cp->ppp->ses, TERM_USER_ERROR, 0); else ppp_layer_passive(ipv6cp->ppp, &ipv6cp->ld); - } else if (!ipv6cp->ppp->ses.terminating) - ap_session_terminate(&ipv6cp->ppp->ses, TERM_USER_ERROR, 0); + } else if (!ipv6cp->ppp->ses.terminating) { + if (conf_ipv6 == IPV6_REQUIRE) + ap_session_terminate(&ipv6cp->ppp->ses, TERM_USER_ERROR, 0); + else + log_ppp_info1("ipv6cp: closed, session continues without IPv6\n"); + } fsm->fsm_state = FSM_Closed; } @@ -292,7 +299,11 @@ static void send_conf_ack(struct ppp_fsm_t *fsm) struct ipv6cp_hdr_t *hdr = (struct ipv6cp_hdr_t*)ipv6cp->ppp->buf; if (ipv6cp->delay_ack) { - send_term_ack(fsm); + /* CCP is still negotiating, withhold the ack until it settles */ + if (ipv6cp->delay_ack_buf) + _free(ipv6cp->delay_ack_buf); + ipv6cp->delay_ack_buf = _malloc(ntohs(hdr->len) + 2); + memcpy(ipv6cp->delay_ack_buf, hdr, ntohs(hdr->len) + 2); return; } @@ -391,10 +402,18 @@ static int ipv6cp_recv_conf_req(struct ppp_ipv6cp_t *ipv6cp, uint8_t *data, int ipv6cp->ropt_len = size; while (size > 0) { + if (size < sizeof(*hdr)) { + log_ppp_warn("IPV6CP: ConfReq: truncated option header (%i bytes left)\n", size); + return IPV6CP_OPT_FAIL; + } + hdr = (struct ipv6cp_opt_hdr_t *)data; - if (!hdr->len || hdr->len > size) - break; + if (hdr->len < sizeof(*hdr) || hdr->len > size) { + log_ppp_warn("IPV6CP: ConfReq: invalid length %i of option %i (%i bytes left)\n", + hdr->len, hdr->id, size); + return IPV6CP_OPT_FAIL; + } ropt = _malloc(sizeof(*ropt)); memset(ropt, 0, sizeof(*ropt)); @@ -503,10 +522,17 @@ static int ipv6cp_recv_conf_rej(struct ppp_ipv6cp_t *ipv6cp, uint8_t *data, int }*/ while (size > 0) { + if (size < sizeof(*hdr)) { + res = -1; + break; + } + hdr = (struct ipv6cp_opt_hdr_t *)data; - if (!hdr->len || hdr->len > size) + if (hdr->len < sizeof(*hdr) || hdr->len > size) { + res = -1; break; + } list_for_each_entry(lopt, &ipv6cp->options, entry) { if (lopt->id == hdr->id) { @@ -544,10 +570,17 @@ static int ipv6cp_recv_conf_nak(struct ppp_ipv6cp_t *ipv6cp, uint8_t *data, int }*/ while (size > 0) { + if (size < sizeof(*hdr)) { + res = -1; + break; + } + hdr = (struct ipv6cp_opt_hdr_t *)data; - if (!hdr->len || hdr->len > size) + if (hdr->len < sizeof(*hdr) || hdr->len > size) { + res = -1; break; + } list_for_each_entry(lopt, &ipv6cp->options, entry) { if (lopt->id == hdr->id) { @@ -587,10 +620,17 @@ static int ipv6cp_recv_conf_ack(struct ppp_ipv6cp_t *ipv6cp, uint8_t *data, int }*/ while (size > 0) { + if (size < sizeof(*hdr)) { + res = -1; + break; + } + hdr = (struct ipv6cp_opt_hdr_t *)data; - if (!hdr->len || hdr->len > size) + if (hdr->len < sizeof(*hdr) || hdr->len > size) { + res = -1; break; + } list_for_each_entry(lopt, &ipv6cp->options, entry) { if (lopt->id == hdr->id) { @@ -671,7 +711,7 @@ static void ipv6cp_recv(struct ppp_handler_t*h) } hdr = (struct ipv6cp_hdr_t *)ipv6cp->ppp->buf; - if (ntohs(hdr->len) < PPP_HEADERLEN) { + if (ntohs(hdr->len) < PPP_HEADERLEN || ntohs(hdr->len) > ipv6cp->ppp->buf_size - 2) { log_ppp_warn("IPV6CP: short packet received\n"); return; } @@ -725,8 +765,10 @@ static void ipv6cp_recv(struct ppp_handler_t*h) ppp_fsm_recv_conf_ack(&ipv6cp->fsm); break; case CONFNAK: - ipv6cp_recv_conf_nak(ipv6cp,(uint8_t*)(hdr + 1), ntohs(hdr->len) - PPP_HDRLEN); - ppp_fsm_recv_conf_rej(&ipv6cp->fsm); + if (ipv6cp_recv_conf_nak(ipv6cp,(uint8_t*)(hdr + 1), ntohs(hdr->len) - PPP_HDRLEN)) + ap_session_terminate(&ipv6cp->ppp->ses, TERM_USER_ERROR, 0); + else + ppp_fsm_recv_conf_rej(&ipv6cp->fsm); break; case CONFREJ: if (ipv6cp_recv_conf_rej(ipv6cp, (uint8_t*)(hdr + 1), ntohs(hdr->len) - PPP_HDRLEN)) @@ -738,7 +780,12 @@ static void ipv6cp_recv(struct ppp_handler_t*h) if (conf_ppp_verbose) log_ppp_info2("recv [IPV6CP TermReq id=%x]\n", hdr->id); ppp_fsm_recv_term_req(&ipv6cp->fsm); - ap_session_terminate(&ipv6cp->ppp->ses, TERM_USER_REQUEST, 0); + /* RFC 1661 sec 3.7: closing one NCP is not sufficient reason + * to terminate the PPP link */ + if (conf_ipv6 == IPV6_REQUIRE) + ap_session_terminate(&ipv6cp->ppp->ses, TERM_USER_REQUEST, 0); + else + ppp_layer_passive(ipv6cp->ppp, &ipv6cp->ld); break; case TERMACK: if (conf_ppp_verbose) @@ -781,6 +828,40 @@ int ipv6cp_option_register(struct ipv6cp_option_handler_t *h) return 0; } +void ipv6cp_ccp_started(struct ppp_t *ppp) +{ + struct ppp_layer_data_t *ld = ppp_find_layer_data(ppp, &ipv6cp_layer); + struct ppp_ipv6cp_t *ipv6cp; + struct ipv6cp_hdr_t *hdr; + + if (!ld) + return; + + ipv6cp = container_of(ld, typeof(*ipv6cp), ld); + + if (!ipv6cp->delay_ack) + return; + + ipv6cp->delay_ack = 0; + + if (ipv6cp->fsm.fsm_state == FSM_Opened) + __ipv6cp_layer_up(ipv6cp); + + if (!ipv6cp->delay_ack_buf) + return; + + hdr = (struct ipv6cp_hdr_t *)ipv6cp->delay_ack_buf; + hdr->code = CONFACK; + + if (conf_ppp_verbose) + log_ppp_info2("send [IPV6CP ConfAck id=%x]\n", hdr->id); + + ppp_unit_send(ipv6cp->ppp, hdr, ntohs(hdr->len) + 2); + + _free(ipv6cp->delay_ack_buf); + ipv6cp->delay_ack_buf = NULL; +} + struct ipv6cp_option_t *ipv6cp_find_option(struct ppp_t *ppp, struct ipv6cp_option_handler_t *h) { struct ppp_ipv6cp_t *ipv6cp = container_of(ppp_find_layer_data(ppp, &ipv6cp_layer), typeof(*ipv6cp), ld); diff --git a/accel-pppd/ppp/ppp_ipv6cp.h b/accel-pppd/ppp/ppp_ipv6cp.h index 6f1789ec..31bdccf6 100644 --- a/accel-pppd/ppp/ppp_ipv6cp.h +++ b/accel-pppd/ppp/ppp_ipv6cp.h @@ -98,12 +98,15 @@ struct ppp_ipv6cp_t struct list_head ropt_list; // last received ConfReq int ropt_len; + void *delay_ack_buf; // ConfAck withheld until CCP finishes + int conf_req_len; unsigned int starting:1; unsigned int started:1; unsigned int delay_ack:1; }; +void ipv6cp_ccp_started(struct ppp_t *ppp); int ipv6cp_option_register(struct ipv6cp_option_handler_t *h); #endif diff --git a/accel-pppd/ppp/ppp_lcp.c b/accel-pppd/ppp/ppp_lcp.c index 10babec8..2424ca94 100644 --- a/accel-pppd/ppp/ppp_lcp.c +++ b/accel-pppd/ppp/ppp_lcp.c @@ -13,6 +13,7 @@ #include "ppp_lcp.h" #include "events.h" #include "iputils.h" +#include "utils.h" #include "memdebug.h" @@ -369,10 +370,18 @@ static int lcp_recv_conf_req(struct ppp_lcp_t *lcp, uint8_t *data, int size) lcp->ropt_len = size; while (size > 0) { + if (size < sizeof(*hdr)) { + log_ppp_warn("LCP: ConfReq: truncated option header (%i bytes left)\n", size); + return LCP_OPT_FAIL; + } + hdr = (struct lcp_opt_hdr_t *)data; - if (!hdr->len || hdr->len > size) - break; + if (hdr->len < sizeof(*hdr) || hdr->len > size) { + log_ppp_warn("LCP: ConfReq: invalid length %i of option %i (%i bytes left)\n", + hdr->len, hdr->id, size); + return LCP_OPT_FAIL; + } ropt = _malloc(sizeof(*ropt)); memset(ropt, 0, sizeof(*ropt)); @@ -461,10 +470,17 @@ static int lcp_recv_conf_rej(struct ppp_lcp_t *lcp, uint8_t *data, int size) } while (size > 0) { + if (size < sizeof(*hdr)) { + res = -1; + break; + } + hdr = (struct lcp_opt_hdr_t *)data; - if (!hdr->len || hdr->len > size) + if (hdr->len < sizeof(*hdr) || hdr->len > size) { + res = -1; break; + } list_for_each_entry(lopt, &lcp->options, entry) { if (lopt->id == hdr->id) { @@ -506,10 +522,17 @@ static int lcp_recv_conf_nak(struct ppp_lcp_t *lcp, uint8_t *data, int size) } while (size > 0) { + if (size < sizeof(*hdr)) { + res = -1; + break; + } + hdr = (struct lcp_opt_hdr_t *)data; - if (!hdr->len || hdr->len > size) + if (hdr->len < sizeof(*hdr) || hdr->len > size) { + res = -1; break; + } list_for_each_entry(lopt,&lcp->options,entry) { if (lopt->id == hdr->id) { @@ -549,10 +572,17 @@ static int lcp_recv_conf_ack(struct ppp_lcp_t *lcp, uint8_t *data, int size) } while (size > 0) { + if (size < sizeof(*hdr)) { + res = -1; + break; + } + hdr = (struct lcp_opt_hdr_t *)data; - if (!hdr->len || hdr->len > size) + if (hdr->len < sizeof(*hdr) || hdr->len > size) { + res = -1; break; + } list_for_each_entry(lopt, &lcp->options, entry) { if (lopt->id == hdr->id) { @@ -586,7 +616,7 @@ static void lcp_recv_echo_repl(struct ppp_lcp_t *lcp, uint8_t *data, int size) if (conf_ppp_verbose) log_ppp_debug("recv [LCP EchoRep id=%x]\n", lcp->fsm.recv_id); } else { - magic = ntohl(*(uint32_t *)data); + magic = u_read_be32(data); if (conf_ppp_verbose) log_ppp_debug("recv [LCP EchoRep id=%x <magic %08x>]\n", lcp->fsm.recv_id, magic); @@ -610,12 +640,13 @@ static void send_echo_reply(struct ppp_lcp_t *lcp) lcp->last_echo_ts = _time(); hdr->code = ECHOREP; - *(uint32_t *)(hdr + 1) = htonl(lcp->magic); + uint32_t magic = htonl(lcp->magic); + memcpy((char *)hdr + sizeof(struct lcp_hdr_t), &magic, sizeof(magic)); if (conf_ppp_verbose) log_ppp_debug("send [LCP EchoRep id=%x <magic %08x>]\n", hdr->id, lcp->magic); - ppp_chan_send(lcp->ppp, hdr, ntohs(hdr->len) + 2); + ppp_chan_send(lcp->ppp, hdr, min(ntohs(hdr->len), lcp->ppp->mtu) + 2); } static void send_echo_request(struct triton_timer_t *t) @@ -733,6 +764,8 @@ static void lcp_recv(struct ppp_handler_t*h) struct ppp_lcp_t *lcp = container_of(h, typeof(*lcp), hnd); int r; char *term_msg; + uint16_t len; + int buf_len; if (lcp->ppp->buf_size < PPP_HEADERLEN + 2) { log_ppp_warn("LCP: short packet received\n"); @@ -740,7 +773,9 @@ static void lcp_recv(struct ppp_handler_t*h) } hdr = (struct lcp_hdr_t *)lcp->ppp->buf; - if (ntohs(hdr->len) < PPP_HEADERLEN) { + len = ntohs(hdr->len); + buf_len = lcp->ppp->buf_size; + if (len < PPP_HEADERLEN || len > lcp->ppp->buf_size - 2) { log_ppp_warn("LCP: short packet received\n"); return; } @@ -796,7 +831,10 @@ static void lcp_recv(struct ppp_handler_t*h) } break; case CONFNAK: - lcp_recv_conf_nak(lcp, (uint8_t*)(hdr + 1), ntohs(hdr->len) - PPP_HDRLEN); + if (lcp_recv_conf_nak(lcp, (uint8_t*)(hdr + 1), ntohs(hdr->len) - PPP_HDRLEN)) { + ap_session_terminate(&lcp->ppp->ses, TERM_USER_ERROR, 0); + break; + } if (lcp->fsm.recv_id != lcp->fsm.id) break; ppp_fsm_recv_conf_rej(&lcp->fsm); @@ -827,25 +865,53 @@ static void lcp_recv(struct ppp_handler_t*h) ppp_fsm_recv_code_rej_bad(&lcp->fsm); break; case ECHOREQ: + if (len < PPP_HDRLEN + 4 || buf_len < (int)(sizeof(*hdr) + 4)) { + log_ppp_warn("LCP: short EchoReq received\n"); + break; + } if (conf_ppp_verbose) - log_ppp_debug("recv [LCP EchoReq id=%x <magic %08x>]\n", hdr->id, ntohl(*(uint32_t*)(hdr + 1))); + log_ppp_debug("recv [LCP EchoReq id=%x <magic %08x>]\n", hdr->id, u_read_be32(hdr + 1)); send_echo_reply(lcp); break; case ECHOREP: + if (len < PPP_HDRLEN + 4 || buf_len < (int)(sizeof(*hdr) + 4)) { + log_ppp_warn("LCP: short EchoRep received\n"); + break; + } lcp_recv_echo_repl(lcp, (uint8_t*)(hdr + 1), ntohs(hdr->len) - PPP_HDRLEN); break; case PROTOREJ: - if (conf_ppp_verbose) - log_ppp_info2("recv [LCP ProtoRej id=%x <%04x>]\n", hdr->id, ntohs(*(uint16_t*)(hdr + 1))); - ppp_recv_proto_rej(lcp->ppp, ntohs(*(uint16_t *)(hdr + 1))); + if (conf_ppp_verbose) { + if (len < PPP_HDRLEN + 2 || buf_len < (int)(sizeof(*hdr) + 2)) { + log_ppp_warn("LCP: short ProtoRej received\n"); + break; + } + log_ppp_info2("recv [LCP ProtoRej id=%x <%04x>]\n", hdr->id, u_read_be16(hdr + 1)); + } + if (len < PPP_HDRLEN + 2 || buf_len < (int)(sizeof(*hdr) + 2)) + break; + ppp_recv_proto_rej(lcp->ppp, u_read_be16(hdr + 1)); break; case DISCARDREQ: - if (conf_ppp_verbose) - log_ppp_info2("recv [LCP DiscardReq id=%x <magic %08x>]\n", hdr->id, ntohl(*(uint32_t*)(hdr + 1))); + if (conf_ppp_verbose) { + if (len < PPP_HDRLEN + 4 || buf_len < (int)(sizeof(*hdr) + 4)) { + log_ppp_warn("LCP: short DiscardReq received\n"); + break; + } + log_ppp_info2("recv [LCP DiscardReq id=%x <magic %08x>]\n", hdr->id, u_read_be32(hdr + 1)); + } break; case IDENT: if (conf_ppp_verbose) { - term_msg = _strndup((char*)(hdr + 1) + 4, ntohs(hdr->len) - 4 - 4); + int id_len; + if (len < PPP_HDRLEN + 4 || buf_len < (int)(sizeof(*hdr) + 4)) { + log_ppp_warn("LCP: short Ident received\n"); + break; + } + id_len = len - PPP_HDRLEN - 4; + if (buf_len < (int)(sizeof(*hdr) + 4 + id_len)) + id_len = buf_len - sizeof(*hdr) - 4; + term_msg = _strndup((char*)(hdr + 1) + 4, id_len); log_ppp_info2("recv [LCP Ident id=%x <%s>]\n", hdr->id, term_msg); _free(term_msg); } diff --git a/accel-pppd/radius/CMakeLists.txt b/accel-pppd/radius/CMakeLists.txt index a0c0d534..3ce72648 100644 --- a/accel-pppd/radius/CMakeLists.txt +++ b/accel-pppd/radius/CMakeLists.txt @@ -22,6 +22,7 @@ ENDIF () ADD_DEFINITIONS(-DDICTIONARY="${CMAKE_INSTALL_PREFIX}/share/accel-ppp/radius/dictionary") ADD_LIBRARY(radius SHARED ${sources}) +TARGET_LINK_LIBRARIES(radius ${crypto_lib}) set_property(TARGET radius PROPERTY CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) set_property(TARGET radius PROPERTY INSTALL_RPATH ${CMAKE_INSTALL_PREFIX}/lib${LIB_SUFFIX}/accel-ppp) diff --git a/accel-pppd/radius/acct.c b/accel-pppd/radius/acct.c index 1cbd9296..0448fc95 100644 --- a/accel-pppd/radius/acct.c +++ b/accel-pppd/radius/acct.c @@ -5,43 +5,49 @@ #include <unistd.h> #include <sys/ioctl.h> #include <netinet/in.h> -#include "linux_ppp.h" -#include "crypto.h" +#include <openssl/md5.h> + +#include "linux_ppp.h" #include "log.h" #include "backup.h" #include "ap_session_backup.h" #include "iputils.h" +#include "utils.h" #include "radius_p.h" #include "memdebug.h" -#ifndef max -#define max(x,y) ((x) > (y) ? (x) : (y)) -#endif - #define INTERIM_SAFE_TIME 10 -static int req_set_RA(struct rad_req_t *req, const char *secret) +static int req_set_RA(struct rad_req_t *req) { + char *secret; MD5_CTX ctx; - if (rad_packet_build(req->pack, req->RA)) + secret = rad_server_secret_dup(req->serv); + if (!secret) return -1; + if (rad_packet_build(req->pack, req->RA)) { + _free(secret); + return -1; + } + MD5_Init(&ctx); MD5_Update(&ctx, req->pack->buf, req->pack->len); MD5_Update(&ctx, secret, strlen(secret)); MD5_Final(req->pack->buf + 4, &ctx); + _free(secret); + return 0; } static int req_set_stat(struct rad_req_t *req, struct ap_session *ses) { - struct rtnl_link_stats64 stats; struct timespec ts; int ret = 0; @@ -50,15 +56,15 @@ static int req_set_stat(struct rad_req_t *req, struct ap_session *ses) else clock_gettime(CLOCK_MONOTONIC, &ts); - if (ap_session_read_stats(ses, &stats) == 0) { - rad_packet_change_int(req->pack, NULL, "Acct-Input-Octets", (int) (stats.rx_bytes & UINT32_MAX)); - rad_packet_change_int(req->pack, NULL, "Acct-Output-Octets", (int) (stats.tx_bytes & UINT32_MAX)); - rad_packet_change_int(req->pack, NULL, "Acct-Input-Packets", (int) (stats.rx_packets & UINT32_MAX)); - rad_packet_change_int(req->pack, NULL, "Acct-Output-Packets", (int) (stats.tx_packets & UINT32_MAX)); - rad_packet_change_int(req->pack, NULL, "Acct-Input-Gigawords", (int) (stats.rx_bytes >> (sizeof(uint32_t) * 8))); - rad_packet_change_int(req->pack, NULL, "Acct-Output-Gigawords", (int) (stats.tx_bytes >> (sizeof(uint32_t) * 8))); - } else - ret = -1; + if (!ses->terminating) + ret = ap_session_read_stats(ses, NULL); + + rad_packet_change_int(req->pack, NULL, "Acct-Input-Octets", (int) (ses->acct_rx_bytes & UINT32_MAX)); + rad_packet_change_int(req->pack, NULL, "Acct-Output-Octets", (int) (ses->acct_tx_bytes & UINT32_MAX)); + rad_packet_change_int(req->pack, NULL, "Acct-Input-Packets", (int) (ses->acct_rx_packets & UINT32_MAX)); + rad_packet_change_int(req->pack, NULL, "Acct-Output-Packets", (int) (ses->acct_tx_packets & UINT32_MAX)); + rad_packet_change_int(req->pack, NULL, "Acct-Input-Gigawords", (int) (ses->acct_rx_bytes >> (sizeof(uint32_t) * 8))); + rad_packet_change_int(req->pack, NULL, "Acct-Output-Gigawords", (int) (ses->acct_tx_bytes >> (sizeof(uint32_t) * 8))); rad_packet_change_int(req->pack, NULL, "Acct-Session-Time", ts.tv_sec - ses->start_time); @@ -70,7 +76,7 @@ static void rad_acct_sent(struct rad_req_t *req, int res) if (res) return; - __sync_add_and_fetch(&req->serv->stat_interim_sent, 1); + rad_server_stat_interim_sent(req->serv); if (!req->hnd.tpd) triton_md_register_handler(req->rpd->ses->ctrl->ctx, &req->hnd); @@ -88,8 +94,7 @@ static void rad_acct_recv(struct rad_req_t *req) int dt = (req->reply->tv.tv_sec - req->pack->tv.tv_sec) * 1000 + (req->reply->tv.tv_nsec - req->pack->tv.tv_nsec) / 1000000; - stat_accm_add(req->serv->stat_interim_query_1m, dt); - stat_accm_add(req->serv->stat_interim_query_5m, dt); + rad_server_stat_interim_query(req->serv, dt); if (req->timeout.tpd) triton_timer_del(&req->timeout); @@ -109,9 +114,7 @@ static void rad_acct_timeout(struct triton_timer_t *t) rad_server_req_exit(req); rad_server_timeout(req->serv); - __sync_add_and_fetch(&req->serv->stat_interim_lost, 1); - stat_accm_add(req->serv->stat_interim_lost_1m, 1); - stat_accm_add(req->serv->stat_interim_lost_5m, 1); + rad_server_stat_interim_lost(req->serv); if (conf_acct_timeout == 0) { triton_timer_del(t); @@ -183,7 +186,7 @@ static void rad_acct_interim_update(struct triton_timer_t *t) rpd->acct_req->pack->id++; if (!rpd->acct_req->before_send) - req_set_RA(rpd->acct_req, rpd->acct_req->serv->secret); + req_set_RA(rpd->acct_req); rpd->acct_req->timeout.expire_tv.tv_sec = conf_timeout; rpd->acct_req->try = 0; @@ -215,7 +218,7 @@ static int rad_acct_before_send(struct rad_req_t *req) clock_gettime(CLOCK_MONOTONIC, &ts); rad_packet_change_int(req->pack, NULL, "Acct-Delay-Time", ts.tv_sec - req->ts + conf_acct_delay_start); - req_set_RA(req, req->serv->secret); + req_set_RA(req); return 0; } @@ -227,7 +230,7 @@ static void rad_acct_start_sent(struct rad_req_t *req, int res) return; } - __sync_add_and_fetch(&req->serv->stat_acct_sent, 1); + rad_server_stat_acct_sent(req->serv); if (!req->hnd.tpd) triton_md_register_handler(req->rpd->ses->ctrl->ctx, &req->hnd); @@ -246,8 +249,7 @@ static void rad_acct_start_recv(struct rad_req_t *req) int dt = (req->reply->tv.tv_sec - req->pack->tv.tv_sec) * 1000 + (req->reply->tv.tv_nsec - req->pack->tv.tv_nsec) / 1000000; - stat_accm_add(req->serv->stat_acct_query_1m, dt); - stat_accm_add(req->serv->stat_acct_query_5m, dt); + rad_server_stat_acct_query(req->serv, dt); triton_timer_del(&req->timeout); @@ -289,9 +291,7 @@ static void rad_acct_start_timeout(struct triton_timer_t *t) rad_server_timeout(req->serv); - __sync_add_and_fetch(&req->serv->stat_acct_lost, 1); - stat_accm_add(req->serv->stat_acct_lost_1m, 1); - stat_accm_add(req->serv->stat_acct_lost_5m, 1); + rad_server_stat_acct_lost(req->serv); if (req->before_send) req->pack->id++; @@ -314,7 +314,7 @@ static int __rad_acct_start(struct radius_pd_t *rpd) if (conf_acct_delay_time) req->before_send = rad_acct_before_send; - else if (req_set_RA(req, req->serv->secret)) + else if (req_set_RA(req)) goto out_err; req->recv = rad_acct_start_recv; @@ -370,11 +370,21 @@ static void rad_acct_stop_sent(struct rad_req_t *req, int res) rpd->acct_req = NULL; } else if (req->rpd) rad_acct_stop_defer(req->rpd); + else { + /* deferred request: the timeout timer is one-shot and + * nobody else references this request, re-arm it to + * retry later, otherwise the request and its socket + * leak */ + if (req->timeout.tpd) + triton_timer_mod(&req->timeout, 0); + else + triton_timer_add(NULL, &req->timeout, 0); + } return; } - __sync_add_and_fetch(&req->serv->stat_acct_sent, 1); + rad_server_stat_acct_sent(req->serv); if (!req->hnd.tpd) triton_md_register_handler(req->rpd ? req->rpd->ses->ctrl->ctx : NULL, &req->hnd); @@ -393,8 +403,7 @@ static void rad_acct_stop_recv(struct rad_req_t *req) int dt = (req->reply->tv.tv_sec - req->pack->tv.tv_sec) * 1000 + (req->reply->tv.tv_nsec - req->pack->tv.tv_nsec) / 1000000; - stat_accm_add(req->serv->stat_acct_query_1m, dt); - stat_accm_add(req->serv->stat_acct_query_5m, dt); + rad_server_stat_acct_query(req->serv, dt); rad_req_free(req); @@ -415,25 +424,34 @@ static void rad_acct_stop_timeout(struct triton_timer_t *t) rad_server_timeout(req->serv); rad_server_req_exit(req); - __sync_add_and_fetch(&req->serv->stat_acct_lost, 1); - stat_accm_add(req->serv->stat_acct_lost_1m, 1); - stat_accm_add(req->serv->stat_acct_lost_5m, 1); + rad_server_stat_acct_lost(req->serv); if (req->before_send) req->pack->id++; } - if (req->try == conf_max_try) { + if (req->try >= conf_max_try) { + if (req->rpd) + req->rpd->acct_req = NULL; rad_req_free(req); return; } if (rad_req_send(req)) { if (ap_shutdown) { + if (req->rpd) + req->rpd->acct_req = NULL; rad_req_free(req); return; } - req->try = 0; + /* no server available at the moment; the timeout timer is + * one-shot, re-arm it to retry later, otherwise the request + * and its socket leak; failed attempts count towards + * conf_max_try so the request is freed above eventually */ + if (req->timeout.tpd) + triton_timer_mod(&req->timeout, 0); + else + triton_timer_add(req->rpd ? req->rpd->ses->ctrl->ctx : NULL, &req->timeout, 0); } } @@ -529,10 +547,10 @@ int rad_acct_stop(struct radius_pd_t *rpd) rad_packet_change_val(req->pack, NULL, "Acct-Status-Type", "Stop"); req_set_stat(req, rpd->ses); - req_set_RA(req, req->serv->secret); + req_set_RA(req); req->recv = rad_acct_stop_recv; - req->timeout.expire = rad_acct_start_timeout; + req->timeout.expire = rad_acct_stop_timeout; req->timeout.expire_tv.tv_sec = conf_timeout; req->sent = rad_acct_stop_sent; req->log = conf_verbose ? log_ppp_info1 : NULL; @@ -544,4 +562,3 @@ int rad_acct_stop(struct radius_pd_t *rpd) return 0; } - diff --git a/accel-pppd/radius/auth.c b/accel-pppd/radius/auth.c index 05058547..d2cb9803 100644 --- a/accel-pppd/radius/auth.c +++ b/accel-pppd/radius/auth.c @@ -1,7 +1,8 @@ #include <stdlib.h> #include <string.h> -#include "crypto.h" +#include <openssl/md5.h> +#include <openssl/sha.h> #include "triton.h" #include "mempool.h" @@ -21,6 +22,7 @@ static int decrypt_chap_mppe_keys(struct rad_req_t *req, struct rad_attr_t *attr uint8_t md5[MD5_DIGEST_LENGTH]; uint8_t sha1[SHA_DIGEST_LENGTH]; uint8_t plain[32]; + char *secret; int i; if (attr->len != 32) { @@ -28,10 +30,14 @@ static int decrypt_chap_mppe_keys(struct rad_req_t *req, struct rad_attr_t *attr return -1; } + secret = rad_server_secret_dup(req->serv); + if (!secret) + return -1; + memcpy(plain, attr->val.octets, 32); MD5_Init(&md5_ctx); - MD5_Update(&md5_ctx, req->serv->secret, strlen(req->serv->secret)); + MD5_Update(&md5_ctx, secret, strlen(secret)); MD5_Update(&md5_ctx, req->pack->buf + 4, 16); MD5_Final(md5, &md5_ctx); @@ -39,7 +45,7 @@ static int decrypt_chap_mppe_keys(struct rad_req_t *req, struct rad_attr_t *attr plain[i] ^= md5[i]; MD5_Init(&md5_ctx); - MD5_Update(&md5_ctx, req->serv->secret, strlen(req->serv->secret)); + MD5_Update(&md5_ctx, secret, strlen(secret)); MD5_Update(&md5_ctx, attr->val.octets, 16); MD5_Final(md5, &md5_ctx); @@ -53,6 +59,7 @@ static int decrypt_chap_mppe_keys(struct rad_req_t *req, struct rad_attr_t *attr SHA1_Final(sha1, &sha1_ctx); memcpy(key, sha1, 16); + _free(secret); return 0; } @@ -62,6 +69,7 @@ static int decrypt_mppe_key(struct rad_req_t *req, struct rad_attr_t *attr, uint MD5_CTX md5_ctx; uint8_t md5[16]; uint8_t plain[32]; + char *secret; int i; if (attr->len != 34) { @@ -74,8 +82,12 @@ static int decrypt_mppe_key(struct rad_req_t *req, struct rad_attr_t *attr, uint return -1; } + secret = rad_server_secret_dup(req->serv); + if (!secret) + return -1; + MD5_Init(&md5_ctx); - MD5_Update(&md5_ctx, req->serv->secret, strlen(req->serv->secret)); + MD5_Update(&md5_ctx, secret, strlen(secret)); MD5_Update(&md5_ctx, req->pack->buf + 4, 16); MD5_Update(&md5_ctx, attr->val.octets, 2); MD5_Final(md5, &md5_ctx); @@ -87,17 +99,19 @@ static int decrypt_mppe_key(struct rad_req_t *req, struct rad_attr_t *attr, uint if (plain[0] != 16) { log_ppp_warn("radius: %s: incorrect key length (%i)\n", attr->attr->name, plain[0]); + _free(secret); return -1; } MD5_Init(&md5_ctx); - MD5_Update(&md5_ctx, req->serv->secret, strlen(req->serv->secret)); + MD5_Update(&md5_ctx, secret, strlen(secret)); MD5_Update(&md5_ctx, attr->val.octets + 2, 16); MD5_Final(md5, &md5_ctx); plain[16] ^= md5[0]; memcpy(key, plain + 1, 16); + _free(secret); return 0; } @@ -171,8 +185,7 @@ static void rad_auth_recv(struct rad_req_t *req) triton_timer_del(&req->timeout); dt = (req->reply->tv.tv_sec - req->pack->tv.tv_sec) * 1000 + (req->reply->tv.tv_nsec - req->pack->tv.tv_nsec) / 1000000; - stat_accm_add(req->serv->stat_auth_query_1m, dt); - stat_accm_add(req->serv->stat_auth_query_5m, dt); + rad_server_stat_auth_query(req->serv, dt); if (pack->code == CODE_ACCESS_ACCEPT) { if (rad_proc_attrs(req)) { @@ -207,9 +220,7 @@ static void rad_auth_timeout(struct triton_timer_t *t) rad_server_timeout(req->serv); - __sync_add_and_fetch(&req->serv->stat_auth_lost, 1); - stat_accm_add(req->serv->stat_auth_lost_1m, 1); - stat_accm_add(req->serv->stat_auth_lost_5m, 1); + rad_server_stat_auth_lost(req->serv); if (rad_req_send(req)) rad_auth_finalize(req->rpd, PWDB_DENIED); @@ -222,7 +233,7 @@ static void rad_auth_sent(struct rad_req_t *req, int res) return; } - __sync_add_and_fetch(&req->serv->stat_auth_sent, 1); + rad_server_stat_auth_sent(req->serv); if (!req->hnd.tpd) triton_md_register_handler(req->rpd->ses->ctrl->ctx, &req->hnd); @@ -277,11 +288,17 @@ int rad_auth_pap(struct radius_pd_t *rpd, const char *username, va_list args) const char *passwd = va_arg(args, const char *); uint8_t *epasswd; int epasswd_len; + char *secret; if (!req) return PWDB_DENIED; - epasswd = encrypt_password(passwd, req->serv->secret, req->RA, &epasswd_len); + secret = rad_server_secret_dup(req->serv); + if (!secret) + return PWDB_DENIED; + + epasswd = encrypt_password(passwd, secret, req->RA, &epasswd_len); + _free(secret); if (!epasswd) return PWDB_DENIED; @@ -433,7 +450,10 @@ static int rad_auth_mschap_v2_recv(struct rad_req_t *req) if (req->reply->code == CODE_ACCESS_ACCEPT) { ra = rad_packet_find_attr(req->reply, "Microsoft", "MS-CHAP2-Success"); if (!ra) { - log_error("radius:auth:mschap-v2: 'MS-CHAP-Success' not found in radius response\n"); + log_error("radius:auth:mschap-v2: 'MS-CHAP2-Success' not found in radius response\n"); + return -1; + } else if (ra->len < 43) { + log_error("radius:auth:mschap-v2: 'MS-CHAP2-Success' too short (%i)\n", ra->len); return -1; } else memcpy(rpd->auth_ctx->authenticator, ra->val.octets + 3, 40); @@ -508,4 +528,3 @@ int rad_auth_null(struct radius_pd_t *rpd, const char *username, va_list args) return PWDB_WAIT; } - diff --git a/accel-pppd/radius/backup.c b/accel-pppd/radius/backup.c index 6d4db636..bfca0e49 100644 --- a/accel-pppd/radius/backup.c +++ b/accel-pppd/radius/backup.c @@ -6,6 +6,7 @@ #include "log.h" #include "memdebug.h" +#include "utils.h" #include "backup.h" #include "ap_session_backup.h" @@ -22,6 +23,7 @@ #define RAD_TAG_ACCT_SERVER_ADDR 9 #define RAD_TAG_ACCT_SERVER_PORT 10 #define RAD_TAG_IDLE_TIMEOUT 11 + #define RAD_TAG_ACCT_USERNAME 12 @@ -94,10 +96,10 @@ static void restore_ipv4_addr(struct ap_session *ses) list_for_each_entry(tag, &m->tag_list, entry) { switch (tag->id) { case SES_TAG_IPV4_ADDR: - ses->ipv4->addr = *(in_addr_t *)tag->data; + ses->ipv4->addr = u_read_native32(tag->data); break; case SES_TAG_IPV4_PEER_ADDR: - ses->ipv4->peer_addr = *(in_addr_t *)tag->data; + ses->ipv4->peer_addr = u_read_native32(tag->data); break; } } @@ -121,16 +123,16 @@ void radius_restore_session(struct ap_session *ses, struct radius_pd_t *rpd) list_for_each_entry(tag, &m->tag_list, entry) { switch (tag->id) { case RAD_TAG_INTERIM_INTERVAL: - rpd->acct_interim_interval = *(uint32_t *)tag->data; + rpd->acct_interim_interval = u_read_native32(tag->data); break; case RAD_TAG_INTERIM_JITTER: - rpd->acct_interim_jitter = *(uint32_t *)tag->data; + rpd->acct_interim_jitter = u_read_native32(tag->data); break; case RAD_TAG_SESSION_TIMEOUT: - rpd->session_timeout.expire_tv.tv_sec = *(uint64_t *)tag->data - ses->start_time; + rpd->session_timeout.expire_tv.tv_sec = u_read_native64(tag->data) - ses->start_time; break; case RAD_TAG_IDLE_TIMEOUT: - rpd->idle_timeout.period = (*(uint32_t *)tag->data) * 1000; + rpd->idle_timeout.period = u_read_native32(tag->data) * 1000; break; case RAD_TAG_IPV4_ADDR: ses->ipv4 = &rpd->ipv4_addr; @@ -150,16 +152,16 @@ void radius_restore_session(struct ap_session *ses, struct radius_pd_t *rpd) rpd->attr_state_len = tag->size; break; case RAD_TAG_TERMINATION_ACTION: - rpd->termination_action = *(uint32_t *)tag->data; + rpd->termination_action = u_read_native32(tag->data); break; case RAD_TAG_ACCT_USERNAME: rpd->acct_username = _strndup(tag->data, tag->size); break; case RAD_TAG_ACCT_SERVER_ADDR: - acct_addr = *(in_addr_t *)tag->data; + acct_addr = u_read_native32(tag->data); break; case RAD_TAG_ACCT_SERVER_PORT: - acct_port = *(uint16_t *)tag->data; + acct_port = u_read_native16(tag->data); break; } } @@ -182,4 +184,3 @@ static void init(void) } DEFINE_INIT(100, init); - diff --git a/accel-pppd/radius/dict.c b/accel-pppd/radius/dict.c index 145c5da2..c181c7cd 100644 --- a/accel-pppd/radius/dict.c +++ b/accel-pppd/radius/dict.c @@ -92,7 +92,7 @@ static int dict_load(const char *fname) continue; r = split(buf, ptr); - if (*ptr[r - 1] == '#') + if (r > 0 && *ptr[r - 1] == '#') r--; if (!strcmp(buf, "VENDOR")) { diff --git a/accel-pppd/radius/dm_coa.c b/accel-pppd/radius/dm_coa.c index e88cc347..df8cdf3e 100644 --- a/accel-pppd/radius/dm_coa.c +++ b/accel-pppd/radius/dm_coa.c @@ -10,7 +10,7 @@ #include <netinet/in.h> #include <sys/socket.h> -#include "crypto.h" +#include <openssl/md5.h> #include "triton.h" #include "events.h" @@ -106,6 +106,7 @@ static int dm_coa_send_nak(int fd, struct rad_packet_t *req, struct sockaddr_in if (err_code) rad_packet_add_int(reply, NULL, "Error-Cause", err_code); + // TODO: We need to add Message-Authenticator attribute here if (rad_packet_build(reply, RA)) { rad_packet_free(reply); return -1; @@ -266,6 +267,15 @@ static int dm_coa_read(struct triton_md_handler_t *h) rad_packet_print(pack, NULL, log_debug); } + if (rad_dae_src_check(addr.sin_addr.s_addr)) { + char ipbuf[INET_ADDRSTRLEN]; + const char *ipstr; + + ipstr = inet_ntop(AF_INET, &addr.sin_addr, ipbuf, sizeof(ipbuf)); + log_warn("radius:dm_coa: source %s not allowed\n", ipstr ? ipstr : "unknown"); + goto out_err_no_reply; + } + if (dm_coa_check_RA(pack, conf_dm_coa_secret)) { log_warn("radius:dm_coa: RA validation failed\n"); goto out_err_no_reply; diff --git a/accel-pppd/radius/packet.c b/accel-pppd/radius/packet.c index 79007036..4a0ab244 100644 --- a/accel-pppd/radius/packet.c +++ b/accel-pppd/radius/packet.c @@ -9,13 +9,29 @@ #include <linux/mman.h> #include <arpa/inet.h> +/* + * Suppress OpenSSL 3.0 deprecation warnings for the HMAC API: it is + * deprecated but still functional, and still required for protocol + * compatibility. The project sets this for every target (see the top level + * CMakeLists.txt); it is repeated here because it only takes effect if it is + * defined before the first OpenSSL header is pulled in. + */ +#define OPENSSL_API_COMPAT 0x10100000L +#include <openssl/hmac.h> +#include <openssl/evp.h> + #include "log.h" #include "mempool.h" - #include "radius_p.h" +#include "attr_defs.h" +#include "utils.h" #include "memdebug.h" +#define HMAC_MD5_LEN 16 +/* Radius header + attribute: type + length */ +#define PACKET_SIGNED_OFFSET (20 + 2) + static mempool_t packet_pool; static mempool_t attr_pool; static mempool_t buf_pool; @@ -47,6 +63,34 @@ void print_buf(uint8_t *buf,int size) printf("\n"); } + +int hmac_md5(const uint8_t *key, size_t key_len, + const uint8_t *data, size_t data_len, + uint8_t out[HMAC_MD5_LEN]) +{ + unsigned int len = 0; + HMAC_CTX *ctx = HMAC_CTX_new(); + if (!ctx) + return -1; + + if (HMAC_Init_ex(ctx, key, (int)key_len, EVP_md5(), NULL) != 1) + goto err; + + if (HMAC_Update(ctx, data, data_len) != 1) + goto err; + + if (HMAC_Final(ctx, out, &len) != 1 || len != HMAC_MD5_LEN) + goto err; + + HMAC_CTX_free(ctx); + return 0; + +err: + HMAC_CTX_free(ctx); + return -1; +} + + int rad_packet_build(struct rad_packet_t *pack, uint8_t *RA) { struct rad_attr_t *attr; @@ -68,20 +112,20 @@ int rad_packet_build(struct rad_packet_t *pack, uint8_t *RA) *ptr = pack->code; ptr++; *ptr = pack->id; ptr++; - *(uint16_t*)ptr = htons(pack->len); ptr+= 2; + u_write_be16(ptr, pack->len); ptr += 2; memcpy(ptr, RA, 16); ptr+=16; list_for_each_entry(attr, &pack->attrs, entry) { if (attr->vendor) { *ptr = 26; ptr++; *ptr = attr->len + 2 + 6; ptr++; - *(uint32_t *)ptr = htonl(attr->vendor->id); ptr+=4; + u_write_be32(ptr, attr->vendor->id); ptr += 4; } *ptr = attr->attr->id; ptr++; *ptr = attr->len + 2; ptr++; switch(attr->attr->type) { case ATTR_TYPE_INTEGER: - *(uint32_t*)ptr = htonl(attr->val.integer); + u_write_be32(ptr, attr->val.integer); break; case ATTR_TYPE_OCTETS: case ATTR_TYPE_STRING: @@ -93,7 +137,7 @@ int rad_packet_build(struct rad_packet_t *pack, uint8_t *RA) memcpy(ptr, &attr->val, attr->len); break; case ATTR_TYPE_DATE: - *(uint32_t*)ptr = htonl(attr->val.date); + u_write_be32(ptr, attr->val.date); break; case ATTR_TYPE_IPV6PREFIX: ptr[0] = 0; @@ -127,9 +171,8 @@ int rad_packet_recv(int fd, struct rad_packet_t **p, struct sockaddr_in *addr) if (!pack) return 0; - //ptr = mmap(NULL, REQ_LENGTH_MAX, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); ptr = mempool_alloc(buf_pool); - if (ptr == MAP_FAILED) { + if (!ptr) { log_emerg("radius:packet: out of memory\n"); goto out_err; } @@ -139,7 +182,7 @@ int rad_packet_recv(int fd, struct rad_packet_t **p, struct sockaddr_in *addr) while (1) { if (addr) - n = recvfrom(fd, pack->buf, REQ_LENGTH_MAX, 0, addr, &addr_len); + n = recvfrom(fd, pack->buf, REQ_LENGTH_MAX, 0, (struct sockaddr *)addr, &addr_len); else n = read(fd, pack->buf, REQ_LENGTH_MAX); if (n < 0) { @@ -162,17 +205,22 @@ int rad_packet_recv(int fd, struct rad_packet_t **p, struct sockaddr_in *addr) pack->code = *ptr; ptr++; pack->id = *ptr; ptr++; - pack->len = ntohs(*(uint16_t*)ptr); ptr += 2; + pack->len = u_read_be16(ptr); ptr += 2; - if (pack->len > n) { + if (pack->len < 20 || pack->len > n) { log_ppp_warn("radius:packet: short packet received %i, expected %i\n", pack->len, n); goto out_err; } ptr += 16; - n -= 20; + n = pack->len - 20; while (n>0) { + if (n < 2) { + log_ppp_warn("radius:packet: truncated attribute header received\n"); + goto out_err; + } + id = *ptr; ptr++; len = *ptr - 2; ptr++; if (len < 0) { @@ -184,20 +232,28 @@ int rad_packet_recv(int fd, struct rad_packet_t **p, struct sockaddr_in *addr) goto out_err; } if (id == 26) { - vendor_id = ntohl(*(uint32_t *)ptr); + if (len < 4) { + log_ppp_warn("radius:packet: vendor attribute too short (%i)\n", len); + goto out_err; + } + vendor_id = u_read_be32(ptr); vendor = rad_dict_find_vendor_id(vendor_id); if (vendor) { + if (len < 4 + vendor->tag + vendor->len) { + log_ppp_warn("radius:packet: vendor %i attribute too short (%i)\n", vendor_id, len); + goto out_err; + } ptr += 4; if (vendor->tag == 2) - id = (uint16_t)ntohs(*(uint16_t *)ptr); + id = u_read_be16(ptr); else id = *ptr; ptr += vendor->tag; if (vendor->len == 2) - len = (uint16_t)ntohs(*(uint16_t *)ptr); + len = u_read_be16(ptr); else len = *ptr; @@ -245,16 +301,23 @@ int rad_packet_recv(int fd, struct rad_packet_t **p, struct sockaddr_in *addr) attr->val.octets = ptr; break; case ATTR_TYPE_INTEGER: - if (len != da->size) + if (len != da->size) { log_ppp_warn("radius:packet: attribute %s has invalid length %i (must be %i)\n", da->name, len, da->size); - case ATTR_TYPE_DATE: + break; + } if (len == 4) - attr->val.integer = ntohl(*(uint32_t*)ptr); + attr->val.integer = u_read_be32(ptr); else if (len == 2) - attr->val.integer = ntohs(*(uint16_t*)ptr); + attr->val.integer = u_read_be16(ptr); else if (len == 1) attr->val.integer = *ptr; break; + case ATTR_TYPE_DATE: + if (len == 4) + attr->val.integer = u_read_be32(ptr); + else + log_ppp_warn("radius:packet: attribute %s has invalid length %i (must be 4)\n", da->name, len); + break; case ATTR_TYPE_IPADDR: case ATTR_TYPE_IFID: case ATTR_TYPE_IPV6ADDR: @@ -302,6 +365,9 @@ void rad_packet_free(struct rad_packet_t *pack) mempool_free(pack->buf); //munmap(pack->buf, REQ_LENGTH_MAX); + if (pack->secret) + _free(pack->secret); + while(!list_empty(&pack->attrs)) { attr = list_entry(pack->attrs.next, typeof(*attr), entry); list_del(&attr->entry); @@ -811,9 +877,21 @@ int rad_packet_send(struct rad_packet_t *pack, int fd, struct sockaddr_in *addr) clock_gettime(CLOCK_MONOTONIC, &pack->tv); + if (pack->secret && pack->message_authenticator) { + uint8_t hmac[HMAC_MD5_LEN]; + uint8_t *ptr = pack->buf; + uint8_t *hmac_ptr = ptr + PACKET_SIGNED_OFFSET; + if (hmac_md5((const uint8_t *)pack->secret, strlen((const char *)pack->secret), pack->buf, pack->len, hmac) < 0) { + log_emerg("radius:packet: failed to calculate HMAC\n"); + return -1; + } + memcpy(hmac_ptr, hmac, HMAC_MD5_LEN); + } + + while (1) { if (addr) - n = sendto(fd, pack->buf, pack->len, 0, addr, sizeof(*addr)); + n = sendto(fd, pack->buf, pack->len, 0, (struct sockaddr *)addr, sizeof(*addr)); else n = write(fd, pack->buf, pack->len); if (n < 0) { diff --git a/accel-pppd/radius/radius.c b/accel-pppd/radius/radius.c index 2406ba04..fa77a916 100644 --- a/accel-pppd/radius/radius.c +++ b/accel-pppd/radius/radius.c @@ -2,6 +2,7 @@ #include <stdlib.h> #include <stdarg.h> #include <stdbool.h> +#include <ctype.h> #include <stdio.h> #include <string.h> #include <unistd.h> @@ -58,102 +59,314 @@ static int conf_strip_realm; const char *conf_attr_tunnel_type; int conf_acct_delay_start; +int conf_blast_protection; +int conf_framed_route_strict; static LIST_HEAD(sessions); static pthread_rwlock_t sessions_lock = PTHREAD_RWLOCK_INITIALIZER; +struct dae_allow_range { + struct list_head entry; + uint32_t begin; + uint32_t end; +}; + +static struct list_head *dae_allow_ranges; +static pthread_rwlock_t dae_allow_lock = PTHREAD_RWLOCK_INITIALIZER; + static void *pd_key; static struct ipdb_t ipdb; static mempool_t rpd_pool; static mempool_t auth_ctx_pool; -static void parse_framed_route(struct radius_pd_t *rpd, const char *attr) +static int rad_add_framed_ipv6_route(const char *str, struct radius_pd_t *rpd); + +static int ipv4_mask_to_prefix(struct in_addr mask, int *plen) { - char str[32]; - char *ptr; - long int prio = 0; - in_addr_t dst; - in_addr_t gw; - int mask; - struct framed_route *fr; + uint32_t m = ntohl(mask.s_addr); + uint32_t inv = ~m; + int p = 0; - ptr = strchr(attr, '/'); - if (ptr && ptr - attr > 16) - goto out_err; + if (inv & (inv + 1)) + return -1; - if (ptr) { - memcpy(str, attr, ptr - attr); - str[ptr - attr] = 0; - } else { - ptr = strchr(attr, ' '); - if (ptr) { - memcpy(str, attr, ptr - attr); - str[ptr - attr] = 0; - } else - strcpy(str, attr); + while (m & 0x80000000) { + p++; + m <<= 1; } - dst = inet_addr(str); - if (dst == INADDR_NONE) - goto out_err; + *plen = p; + return 0; +} + +static int parse_framed_route_v4(const char *str, struct framed_route *fr) +{ + const char *ptr; + size_t len; + struct in_addr dst; + struct in_addr gw; + struct in_addr mask_addr; + uint8_t plen; + uint32_t prio; + uint32_t mask = 0; + uint32_t addr_hbo; + + // Take a steady breath and skip leading RFC-style spaces so everything starts clean. + ptr = str + u_parse_spaces(str); + + len = u_parse_ip4cidr(ptr, &dst, &plen); + if (len) { + // Happy path: CIDR tells us exactly what we need. + fr->dst = dst.s_addr; + fr->mask = plen; + mask = plen ? (0xffffffffu << (32 - plen)) : 0; + ptr += len; + } else { + // If CIDR didn't show up, we gently switch to plain IPv4 and optional mask. + len = u_parse_ip4addr(ptr, &dst); + if (!len) + return -1; + fr->dst = dst.s_addr; + ptr += len; - if (ptr) { if (*ptr == '/') { - char *ptr2; - for (ptr2 = ++ptr; *ptr2 && *ptr2 != '.' && *ptr2 != ' '; ptr2++); - if (*ptr2 == '.' && ptr2 - ptr <= 16) { - in_addr_t a; - memcpy(str, ptr, ptr2 - ptr); - str[ptr2 - ptr] = 0; - a = ntohl(inet_addr(str)); - if (a == INADDR_NONE) - goto out_err; - mask = 33 - htonl(inet_addr(str)); - if (~((1<<(32 - mask)) - 1) != a) - goto out_err; - } else if (*ptr2 == ' ' || *ptr2 == 0) { - char *ptr3; - mask = strtol(ptr, &ptr3, 10); - if (mask < 0 || mask > 32 || ptr3 != ptr2) - goto out_err; - } else - goto out_err; + ptr++; + len = u_parse_ip4addr(ptr, &mask_addr); + if (len) { + if (ipv4_mask_to_prefix(mask_addr, &fr->mask)) + return -1; + mask = ntohl(mask_addr.s_addr); + ptr += len; + } else { + len = u_parse_u8(ptr, &plen); + if (!len || plen > 32) + return -1; + fr->mask = plen; + mask = plen ? (0xffffffffu << (32 - plen)) : 0; + ptr += len; + } } else - mask = 32; - - for (++ptr; *ptr && *ptr != ' '; ptr++); - if (*ptr == ' ') - gw = inet_addr(ptr + 1); - else if (*ptr == 0) - gw = 0; - else - goto out_err; - - /* Parse priority, if any */ - if (*ptr) { - for (++ptr; *ptr && *ptr != ' '; ptr++); - if (*ptr == ' ') - if (u_readlong(&prio, ptr + 1, 0, UINT32_MAX) < 0) - goto out_err; - } - } else { - mask = 32; - gw = 0; + fr->mask = 32; + } + + if (!mask) + mask = fr->mask ? (0xffffffffu << (32 - fr->mask)) : 0; + + if (conf_framed_route_strict) { + addr_hbo = ntohl(fr->dst); + if (addr_hbo & ~mask) + return -1; + } + + // If the string ends here, we can relax: no gateway or metric specified. + ptr += u_parse_spaces(ptr); + if (u_parse_endstr(ptr)) { + fr->gw = 0; + fr->prio = 0; + return 0; } - fr = _malloc(sizeof (*fr)); - fr->dst = dst; - fr->mask = mask; - fr->gw = gw; + len = u_parse_ip4addr(ptr, &gw); + if (!len) + return -1; + fr->gw = gw.s_addr; + ptr += len; + + ptr += u_parse_spaces(ptr); + if (u_parse_endstr(ptr)) { + fr->prio = 0; + return 0; + } + + len = u_parse_u32(ptr, &prio); + if (!len) + return -1; + ptr += len; + + if (!u_parse_endstr(ptr)) + return -1; + fr->prio = prio; + return 0; +} + +static void parse_framed_route(struct radius_pd_t *rpd, const char *attr) +{ + struct framed_route *fr; + + /* RFC 2865: Framed-Route is IPv4-only and uses spaces; IPv6 lives in Framed-IPv6-Route. */ + if (strchr(attr, ':')) { + log_ppp_warn("radius: Framed-Route is IPv4-only per RFC 2865, use Framed-IPv6-Route for %s\n", attr); + return; + } + + fr = _malloc(sizeof(*fr)); + if (!fr) + goto out_err; + memset(fr, 0, sizeof(*fr)); + + if (parse_framed_route_v4(attr, fr) < 0) + goto out_err_free; + fr->next = rpd->fr; rpd->fr = fr; return; +out_err_free: + _free(fr); out_err: - log_ppp_warn("radius: failed to parse Framed-Route=%s\n", attr); + log_ppp_warn("radius: failed to parse Framed-Route=\"%s\" (expected \"dst[/mask] gw [metric]\"; check destination, mask, gateway, and metric fields)\n", attr); +} + +static char *trim_spaces(char *str) +{ + char *end; + + while (isspace((unsigned char)*str)) + ++str; + + if (*str == '\0') + return str; + + end = str + strlen(str) - 1; + while (end > str && isspace((unsigned char)*end)) { + *end = '\0'; + --end; + } + + return str; +} + +static void dae_allow_clear(void) +{ + struct dae_allow_range *range; + + if (!dae_allow_ranges) + return; + + while (!list_empty(dae_allow_ranges)) { + range = list_first_entry(dae_allow_ranges, typeof(*range), entry); + list_del(&range->entry); + _free(range); + } + + _free(dae_allow_ranges); + dae_allow_ranges = NULL; +} + +static int dae_allow_add(uint32_t begin, uint32_t end) +{ + struct dae_allow_range *range; + + if (!dae_allow_ranges) + return -1; + + range = _malloc(sizeof(*range)); + if (!range) + return -1; + + range->begin = begin; + range->end = end; + list_add_tail(&range->entry, dae_allow_ranges); + + return 0; +} + +static int dae_allow_parse_token(const char *token, uint32_t *begin, uint32_t *end) +{ + struct in_addr base_addr; + uint8_t suffix; + size_t len; + + len = u_parse_ip4cidr(token, &base_addr, &suffix); + if (len && token[len] == '\0') { + uint32_t addr_hbo = ntohl(base_addr.s_addr); + uint32_t mask = (uint64_t)0xffffffff << (32 - suffix); + uint32_t ip_min = addr_hbo & mask; + + *begin = ip_min; + *end = addr_hbo | ~mask; + if (ip_min != addr_hbo) { + struct in_addr min_addr = { .s_addr = htonl(ip_min) }; + char ipbuf[INET_ADDRSTRLEN]; + + log_warn("radius: dae-allowed network %s is equivalent to %s/%hhu\n", + token, u_ip4str(&min_addr, ipbuf), suffix); + } + return 0; + } + + if (!inet_aton(token, &base_addr)) + return -1; + + *begin = ntohl(base_addr.s_addr); + *end = *begin; + return 0; +} + +static int dae_allow_parse(const char *opt, int *entries) +{ + char *dup; + char *token; + char *saveptr; + int count = 0; + + dup = _strdup(opt); + if (!dup) + return -1; + + for (token = strtok_r(dup, ",", &saveptr); token; + token = strtok_r(NULL, ",", &saveptr)) { + char *trimmed = trim_spaces(token); + uint32_t begin; + uint32_t end; + + if (*trimmed == '\0') + continue; + + if (dae_allow_parse_token(trimmed, &begin, &end)) { + log_warn("radius: dae-allowed invalid entry \"%s\"\n", trimmed); + continue; + } + + if (dae_allow_add(begin, end)) { + _free(dup); + return -1; + } + + ++count; + } + + _free(dup); + *entries = count; + return 0; +} + +static int dae_allow_check(in_addr_t ipaddr) +{ + struct dae_allow_range *range; + uint32_t addr = ntohl(ipaddr); + + list_for_each_entry(range, dae_allow_ranges, entry) { + if (addr >= range->begin && addr <= range->end) + return 0; + } + + return -1; +} + +int rad_dae_src_check(in_addr_t ipaddr) +{ + int res = 0; + + pthread_rwlock_rdlock(&dae_allow_lock); + if (dae_allow_ranges) + res = dae_allow_check(ipaddr); + pthread_rwlock_unlock(&dae_allow_lock); + + return res; } /* Parse a RADIUS Framed-IPv6-Route string. @@ -275,12 +488,31 @@ err: return -1; } +/* + * Number of IPv6 DNS servers kept per session. Matches the number of dns= + * options the ipv6_nd and ipv6_dhcp modules accept in [ipv6-dns], and keeps + * the RDNSS option of a router advertisement to a sane size. + */ +#define MAX_DNS6_COUNT 3 + +static void free_ipv6_dns(struct radius_pd_t *rpd) +{ + struct ipv6db_addr_t *a; + + while (!list_empty(&rpd->ipv6_dns.addr_list)) { + a = list_entry(rpd->ipv6_dns.addr_list.next, typeof(*a), entry); + list_del(&a->entry); + _free(a); + } +} + int rad_proc_attrs(struct rad_req_t *req) { struct ev_wins_t wins = {}; struct ev_dns_t dns = {}; struct rad_attr_t *attr; struct ipv6db_addr_t *a; + int dns6_count = -1; int res = 0; struct radius_pd_t *rpd = req->rpd; @@ -389,6 +621,28 @@ int rad_proc_attrs(struct rad_req_t *req) a->addr = attr->val.ipv6prefix.prefix; list_add_tail(&a->entry, &rpd->ipv6_dp.prefix_list); break; + case DNS_Server_IPv6_Address: + if (dns6_count < 0) { + /* This reply carries a DNS server list of + its own, it replaces whatever a previous + one assigned */ + free_ipv6_dns(rpd); + dns6_count = 0; + } + if (dns6_count >= MAX_DNS6_COUNT) { + if (dns6_count == MAX_DNS6_COUNT) + log_ppp_warn("radius: ignoring DNS-Server-IPv6-Address" + " beyond the first %i\n", MAX_DNS6_COUNT); + dns6_count++; + break; + } + a = _malloc(sizeof(*a)); + memset(a, 0, sizeof(*a)); + a->prefix_len = 128; + a->addr = attr->val.ipv6addr; + list_add_tail(&a->entry, &rpd->ipv6_dns.addr_list); + dns6_count++; + break; case NAS_Port: rpd->ses->unit_idx = attr->val.integer; break; @@ -422,6 +676,11 @@ int rad_proc_attrs(struct rad_req_t *req) if (!rpd->ses->ipv6_dp && !list_empty(&rpd->ipv6_dp.prefix_list)) rpd->ses->ipv6_dp = &rpd->ipv6_dp; + /* Like the IPv4 DNS servers, absent attributes leave whatever a + previous reply assigned in place */ + if (!list_empty(&rpd->ipv6_dns.addr_list)) + rpd->ses->ipv6_dns = &rpd->ipv6_dns; + return res; } @@ -586,6 +845,7 @@ static void ses_starting(struct ap_session *ses) INIT_LIST_HEAD(&rpd->plugin_list); INIT_LIST_HEAD(&rpd->ipv6_addr.addr_list); INIT_LIST_HEAD(&rpd->ipv6_dp.prefix_list); + INIT_LIST_HEAD(&rpd->ipv6_dns.addr_list); rpd->ipv4_addr.owner = &ipdb; rpd->ipv6_addr.owner = &ipdb; @@ -641,8 +901,7 @@ static void ses_started(struct ap_session *ses) bool gw_spec = !IN6_IS_ADDR_UNSPECIFIED(&fr6->gw); char nbuf[INET6_ADDRSTRLEN]; char gwbuf[INET6_ADDRSTRLEN]; - - if (ip6route_add(gw_spec ? 0 : rpd->ses->ifindex, &fr6->prefix, fr6->plen, gw_spec ? &fr6->gw : NULL, 3, fr6->prio)) { + if (ip6route_add(gw_spec ? 0 : rpd->ses->ifindex, &fr6->prefix, fr6->plen, gw_spec ? &fr6->gw : NULL, 3, fr6->prio, rpd->ses->vrf_name)) { log_ppp_warn("radius: failed to add route %s/%hhu %s %u\n", u_ip6str(&fr6->prefix, nbuf), fr6->plen, u_ip6str(&fr6->gw, gwbuf), fr6->prio); @@ -650,7 +909,7 @@ static void ses_started(struct ap_session *ses) } for (fr = rpd->fr; fr; fr = fr->next) { - if (iproute_add(fr->gw ? 0 : rpd->ses->ifindex, 0, fr->dst, fr->gw, 3, fr->mask, fr->prio)) { + if (iproute_add(fr->gw ? 0 : rpd->ses->ifindex, 0, fr->dst, fr->gw, 3, fr->mask, fr->prio, rpd->ses->vrf_name)) { char dst[17], gw[17]; u_inet_ntoa(fr->dst, dst); u_inet_ntoa(fr->gw, gw); @@ -689,12 +948,12 @@ static void ses_finishing(struct ap_session *ses) * when the interface is removed. */ if (!IN6_IS_ADDR_UNSPECIFIED(&fr6->gw)) - ip6route_del(0, &fr6->prefix, fr6->plen, &fr6->gw, 3, fr6->prio); + ip6route_del(0, &fr6->prefix, fr6->plen, &fr6->gw, 3, fr6->prio, rpd->ses->vrf_name); } for (fr = rpd->fr; fr; fr = fr->next) { if (fr->gw) - iproute_del(0, 0, fr->dst, fr->gw, 3, fr->mask, fr->prio); + iproute_del(0, 0, fr->dst, fr->gw, 3, fr->mask, fr->prio, rpd->ses->vrf_name); } if (rpd->acct_started || rpd->acct_req) @@ -769,6 +1028,9 @@ static void ses_finished(struct ap_session *ses) _free(a); } + ses->ipv6_dns = NULL; + free_ipv6_dns(rpd); + fr6 = rpd->fr6; while (fr6) { struct framed_ip6_route *next = fr6->next; @@ -1046,6 +1308,42 @@ static int load_config(void) return -1; } + { + int entries = 0; + int dae_allowed_present = 0; + int parse_rc = 0; + + pthread_rwlock_wrlock(&dae_allow_lock); + dae_allow_clear(); + opt = conf_get_opt("radius", "dae-allowed"); + if (opt) { + dae_allowed_present = 1; + dae_allow_ranges = _malloc(sizeof(*dae_allow_ranges)); + if (!dae_allow_ranges) { + log_emerg("radius: failed to allocate dae-allowed list\n"); + parse_rc = -1; + goto dae_allow_unlock; + } + INIT_LIST_HEAD(dae_allow_ranges); + if (dae_allow_parse(opt, &entries)) { + log_emerg("radius: failed to parse dae-allowed\n"); + dae_allow_clear(); + parse_rc = -1; + goto dae_allow_unlock; + } + } + + if (dae_allowed_present && entries == 0) { + log_warn("radius: dae-allowed has no valid entries, DAE source restrictions are disabled\n"); + dae_allow_clear(); + } + +dae_allow_unlock: + pthread_rwlock_unlock(&dae_allow_lock); + if (parse_rc) + return -1; + } + opt = conf_get_opt("radius", "sid-in-auth"); if (opt) conf_sid_in_auth = atoi(opt); @@ -1086,6 +1384,18 @@ static int load_config(void) else conf_acct_delay_start = 0; + opt = conf_get_opt("radius", "blast-protection"); + if (opt && atoi(opt) > 0) { + conf_blast_protection = 1; + } else { + conf_blast_protection = 0; + } + opt = conf_get_opt("radius", "framed-route-strict"); + if (opt && atoi(opt) > 0) + conf_framed_route_strict = 1; + else + conf_framed_route_strict = 0; + return 0; } diff --git a/accel-pppd/radius/radius.h b/accel-pppd/radius/radius.h index 1925c973..c19b25c2 100644 --- a/accel-pppd/radius/radius.h +++ b/accel-pppd/radius/radius.h @@ -103,6 +103,8 @@ struct rad_attr_t struct rad_packet_t { + int message_authenticator; // 1 if message authenticator is required + uint8_t *secret; // shared secret for this packet for Message-Authenticator signature int code; uint8_t id; int len; diff --git a/accel-pppd/radius/radius_p.h b/accel-pppd/radius/radius_p.h index 87613624..e4b84740 100644 --- a/accel-pppd/radius/radius_p.h +++ b/accel-pppd/radius/radius_p.h @@ -65,6 +65,7 @@ struct radius_pd_t { struct ipv4db_item_t ipv4_addr; struct ipv6db_item_t ipv6_addr; struct ipv6db_prefix_t ipv6_dp; + struct ipv6db_item_t ipv6_dns; int acct_interim_interval; int acct_interim_jitter; @@ -115,6 +116,31 @@ struct rad_req_t { void (*log)(const char *fmt, ...); }; +struct rad_server_stat_t { + unsigned long auth_sent; + unsigned long auth_lost; + unsigned long acct_sent; + unsigned long acct_lost; + unsigned long interim_sent; + unsigned long interim_lost; + unsigned long fail_cnt; + + struct stat_accm_t *auth_lost_1m; + struct stat_accm_t *auth_lost_5m; + struct stat_accm_t *auth_query_1m; + struct stat_accm_t *auth_query_5m; + + struct stat_accm_t *acct_lost_1m; + struct stat_accm_t *acct_lost_5m; + struct stat_accm_t *acct_query_1m; + struct stat_accm_t *acct_query_5m; + + struct stat_accm_t *interim_lost_1m; + struct stat_accm_t *interim_lost_5m; + struct stat_accm_t *interim_query_1m; + struct stat_accm_t *interim_query_5m; +}; + struct rad_server_t { struct list_head entry; struct triton_context_t ctx; @@ -139,28 +165,7 @@ struct rad_server_t { int weight; pthread_mutex_t lock; - unsigned long stat_auth_sent; - unsigned long stat_auth_lost; - unsigned long stat_acct_sent; - unsigned long stat_acct_lost; - unsigned long stat_interim_sent; - unsigned long stat_interim_lost; - unsigned long stat_fail_cnt; - - struct stat_accm_t *stat_auth_lost_1m; - struct stat_accm_t *stat_auth_lost_5m; - struct stat_accm_t *stat_auth_query_1m; - struct stat_accm_t *stat_auth_query_5m; - - struct stat_accm_t *stat_acct_lost_1m; - struct stat_accm_t *stat_acct_lost_5m; - struct stat_accm_t *stat_acct_query_1m; - struct stat_accm_t *stat_acct_query_5m; - - struct stat_accm_t *stat_interim_lost_1m; - struct stat_accm_t *stat_interim_lost_5m; - struct stat_accm_t *stat_interim_query_1m; - struct stat_accm_t *stat_interim_query_5m; + struct rad_server_stat_t stat; unsigned int backup:1; unsigned int starting:1; @@ -198,6 +203,7 @@ extern int conf_acct_interim_jitter; extern int conf_accounting; extern const char *conf_attr_tunnel_type; extern int conf_acct_delay_start; +extern int conf_blast_protection; int rad_check_nas_pack(struct rad_packet_t *pack); struct radius_pd_t *rad_find_session(const char *sessionid, const char *username, const char *port_id, int port, in_addr_t ipaddr, const char *csid); @@ -210,7 +216,7 @@ struct rad_req_t *rad_req_alloc(struct radius_pd_t *rpd, int code, const char *u struct rad_req_t *rad_req_alloc2(struct radius_pd_t *rpd, int code, const char *username, in_addr_t addr, int port); struct rad_req_t *rad_req_alloc_empty(); int rad_req_acct_fill(struct rad_req_t *); -void rad_req_free(struct rad_req_t *); +struct rad_server_t *rad_req_free(struct rad_req_t *); int rad_req_send(struct rad_req_t *req); int __rad_req_send(struct rad_req_t *req, int async); int rad_req_read(struct triton_md_handler_t *h); @@ -239,10 +245,12 @@ void rad_packet_print(struct rad_packet_t *pack, struct rad_server_t *s, void (* int rad_packet_send(struct rad_packet_t *pck, int fd, struct sockaddr_in *addr); void dm_coa_cancel(struct radius_pd_t *pd); +int rad_dae_src_check(in_addr_t ipaddr); struct rad_server_t *rad_server_get(int); struct rad_server_t *rad_server_get2(int, in_addr_t, int); -void rad_server_put(struct rad_server_t *, int); +struct rad_server_t *rad_server_put(struct rad_server_t *, int); +char *rad_server_secret_dup(struct rad_server_t *); int rad_server_req_enter(struct rad_req_t *); void rad_server_req_exit(struct rad_req_t *); int rad_server_req_cancel(struct rad_req_t *, int full); @@ -250,6 +258,16 @@ int rad_server_realloc(struct rad_req_t *); void rad_server_fail(struct rad_server_t *); void rad_server_timeout(struct rad_server_t *); void rad_server_reply(struct rad_server_t *); +void rad_server_stat_fail(struct rad_server_t *); +void rad_server_stat_auth_sent(struct rad_server_t *); +void rad_server_stat_auth_lost(struct rad_server_t *); +void rad_server_stat_auth_query(struct rad_server_t *, unsigned int dt); +void rad_server_stat_acct_sent(struct rad_server_t *); +void rad_server_stat_acct_lost(struct rad_server_t *); +void rad_server_stat_acct_query(struct rad_server_t *, unsigned int dt); +void rad_server_stat_interim_sent(struct rad_server_t *); +void rad_server_stat_interim_lost(struct rad_server_t *); +void rad_server_stat_interim_query(struct rad_server_t *, unsigned int dt); void rad_update_session_timeout(struct radius_pd_t *rpd, int timeout); @@ -266,4 +284,3 @@ unsigned long stat_accm_get_cnt(struct stat_accm_t *); unsigned long stat_accm_get_avg(struct stat_accm_t *); #endif - diff --git a/accel-pppd/radius/req.c b/accel-pppd/radius/req.c index be74f047..72c46b16 100644 --- a/accel-pppd/radius/req.c +++ b/accel-pppd/radius/req.c @@ -15,6 +15,8 @@ #include "memdebug.h" +#define HMAC_MD5_LEN 16 + static int make_socket(struct rad_req_t *req); static mempool_t req_pool; @@ -73,6 +75,19 @@ static struct rad_req_t *__rad_req_alloc(struct radius_pd_t *rpd, int code, cons if (!req->pack) goto out_err; + if (code == CODE_ACCESS_REQUEST && conf_blast_protection) { + uint8_t buf[HMAC_MD5_LEN] = {0}; + req->pack->message_authenticator = 1; + req->pack->secret = (uint8_t *)rad_server_secret_dup(req->serv); + if (!req->pack->secret) + goto out_err; + if (rad_packet_add_octets(req->pack, NULL, "Message-Authenticator", buf, HMAC_MD5_LEN)) { + _free(req->pack->secret); + req->pack->secret = NULL; + goto out_err; + } + } + if (code == CODE_ACCOUNTING_REQUEST && rpd->acct_username) username = rpd->acct_username; @@ -242,13 +257,15 @@ int rad_req_acct_fill(struct rad_req_t *req) return 0; } -void rad_req_free(struct rad_req_t *req) +struct rad_server_t *rad_req_free(struct rad_req_t *req) { + struct rad_server_t *s = NULL; + assert(!req->active); assert(!req->entry.next); if (req->serv) - rad_server_put(req->serv, req->type); + s = rad_server_put(req->serv, req->type); if (req->hnd.tpd) triton_md_unregister_handler(&req->hnd, 1); @@ -265,6 +282,8 @@ void rad_req_free(struct rad_req_t *req) rad_packet_free(req->reply); mempool_free(req); + + return s; } static int make_socket(struct rad_req_t *req) diff --git a/accel-pppd/radius/serv.c b/accel-pppd/radius/serv.c index 4af6a67a..71398c82 100644 --- a/accel-pppd/radius/serv.c +++ b/accel-pppd/radius/serv.c @@ -11,14 +11,14 @@ #include <netinet/in.h> #include <arpa/inet.h> +#include <openssl/md5.h> + #include "log.h" #include "triton.h" #include "events.h" #include "cli.h" #include "utils.h" -#include "crypto.h" - #include "radius_p.h" #include "memdebug.h" @@ -93,16 +93,35 @@ struct rad_server_t *rad_server_get2(int type, in_addr_t addr, int port) return __rad_server_get(type, NULL, addr, port); } -void rad_server_put(struct rad_server_t *s, int type) +struct rad_server_t *rad_server_put(struct rad_server_t *s, int type) { + int do_close = 0; + int do_free = 0; + __sync_sub_and_fetch(&s->client_cnt[type], 1); if ((s->need_free || s->need_close) && !s->client_cnt[0] && !s->client_cnt[1]) { - if (s->need_close) + if (s->need_close) { + do_close = 1; triton_context_call(&s->ctx, (triton_event_func)serv_ctx_close, &s->ctx); - else + } else { + do_free = 1; __free_server(s); - } + } + } + + return (do_free || do_close) ? NULL : s; +} + +char *rad_server_secret_dup(struct rad_server_t *s) +{ + char *secret; + + pthread_mutex_lock(&s->lock); + secret = _strdup(s->secret); + pthread_mutex_unlock(&s->lock); + + return secret; } static void req_wakeup(struct rad_req_t *req) @@ -133,7 +152,20 @@ static void req_wakeup(struct rad_req_t *req) } pthread_mutex_unlock(&req->serv->lock); - req->send(req, 1); + if (req->send(req, 1) == -2) { + /* socket setup failed: release the slot taken in + * rad_server_req_exit() and drive the failover path, + * otherwise the server's req_cnt leaks and the request + * is orphaned */ + req->active = 0; + pthread_mutex_lock(&req->serv->lock); + req->serv->req_cnt--; + pthread_mutex_unlock(&req->serv->lock); + + rad_server_fail(req->serv); + + req->send(req, -1); + } } static void req_wakeup_failed(struct rad_req_t *req) @@ -338,7 +370,7 @@ void rad_server_fail(struct rad_server_t *s) } s->queue_cnt = 0; - s->stat_fail_cnt++; + rad_server_stat_fail(s); pthread_mutex_unlock(&s->lock); } @@ -358,18 +390,86 @@ void rad_server_reply(struct rad_server_t *s) s->timeout_cnt = 0; } -static int req_set_RA(struct rad_req_t *req, const char *secret) +void rad_server_stat_fail(struct rad_server_t *s) +{ + __atomic_add_fetch(&s->stat.fail_cnt, 1, __ATOMIC_RELAXED); +} + +void rad_server_stat_auth_sent(struct rad_server_t *s) +{ + __atomic_add_fetch(&s->stat.auth_sent, 1, __ATOMIC_RELAXED); +} + +void rad_server_stat_auth_lost(struct rad_server_t *s) +{ + __atomic_add_fetch(&s->stat.auth_lost, 1, __ATOMIC_RELAXED); + stat_accm_add(s->stat.auth_lost_1m, 1); + stat_accm_add(s->stat.auth_lost_5m, 1); +} + +void rad_server_stat_auth_query(struct rad_server_t *s, unsigned int dt) +{ + stat_accm_add(s->stat.auth_query_1m, dt); + stat_accm_add(s->stat.auth_query_5m, dt); +} + +void rad_server_stat_acct_sent(struct rad_server_t *s) +{ + __atomic_add_fetch(&s->stat.acct_sent, 1, __ATOMIC_RELAXED); +} + +void rad_server_stat_acct_lost(struct rad_server_t *s) +{ + __atomic_add_fetch(&s->stat.acct_lost, 1, __ATOMIC_RELAXED); + stat_accm_add(s->stat.acct_lost_1m, 1); + stat_accm_add(s->stat.acct_lost_5m, 1); +} + +void rad_server_stat_acct_query(struct rad_server_t *s, unsigned int dt) +{ + stat_accm_add(s->stat.acct_query_1m, dt); + stat_accm_add(s->stat.acct_query_5m, dt); +} + +void rad_server_stat_interim_sent(struct rad_server_t *s) +{ + __atomic_add_fetch(&s->stat.interim_sent, 1, __ATOMIC_RELAXED); +} + +void rad_server_stat_interim_lost(struct rad_server_t *s) +{ + __atomic_add_fetch(&s->stat.interim_lost, 1, __ATOMIC_RELAXED); + stat_accm_add(s->stat.interim_lost_1m, 1); + stat_accm_add(s->stat.interim_lost_5m, 1); +} + +void rad_server_stat_interim_query(struct rad_server_t *s, unsigned int dt) +{ + stat_accm_add(s->stat.interim_query_1m, dt); + stat_accm_add(s->stat.interim_query_5m, dt); +} + +static int req_set_RA(struct rad_req_t *req) { + char *secret; MD5_CTX ctx; - if (rad_packet_build(req->pack, req->RA)) + secret = rad_server_secret_dup(req->serv); + if (!secret) return -1; + if (rad_packet_build(req->pack, req->RA)) { + _free(secret); + return -1; + } + MD5_Init(&ctx); MD5_Update(&ctx, req->pack->buf, req->pack->len); MD5_Update(&ctx, secret, strlen(secret)); MD5_Final(req->pack->buf + 4, &ctx); + _free(secret); + return 0; } @@ -383,13 +483,14 @@ static void acct_on_sent(struct rad_req_t *req, int res) static void acct_on_recv(struct rad_req_t *req) { - struct rad_server_t *s = req->serv; + struct rad_server_t *s = rad_req_free(req); - rad_req_free(req); + if (!s) + return; - if (req->serv->starting) { - req->serv->starting = 0; - req->serv->acct_on = 1; + if (s->starting) { + s->starting = 0; + s->acct_on = 1; } else __free_server(s); } @@ -397,12 +498,15 @@ static void acct_on_recv(struct rad_req_t *req) static void acct_on_timeout(struct triton_timer_t *t) { struct rad_req_t *req = container_of(t, typeof(*req), timeout); - struct rad_server_t *s = req->serv; log_switch(triton_context_self(), NULL); if (req->try++ == conf_max_try) { - rad_req_free(req); + struct rad_server_t *s = rad_req_free(req); + + if (!s) + return; + if (s->starting) s->starting = 0; else @@ -416,6 +520,8 @@ static void acct_on_timeout(struct triton_timer_t *t) static void send_acct_on(struct rad_server_t *s) { struct rad_req_t *req = rad_req_alloc_empty(); + if (!req) + return; log_switch(triton_context_self(), NULL); @@ -450,7 +556,7 @@ static void send_acct_on(struct rad_server_t *s) if (rad_packet_add_ipaddr(req->pack, NULL, "NAS-IP-Address", conf_nas_ip_address)) goto out_err; - if (req_set_RA(req, s->secret)) + if (req_set_RA(req)) goto out_err; __rad_req_send(req, 0); @@ -483,10 +589,66 @@ static void serv_ctx_close(struct triton_context_t *ctx) } } +struct rad_server_stat_snapshot_t { + int req_cnt; + int queue_cnt; + unsigned long auth_sent; + unsigned long auth_lost; + unsigned long auth_lost_1m; + unsigned long auth_lost_5m; + unsigned long auth_query_1m; + unsigned long auth_query_5m; + unsigned long acct_sent; + unsigned long acct_lost; + unsigned long acct_lost_1m; + unsigned long acct_lost_5m; + unsigned long acct_query_1m; + unsigned long acct_query_5m; + unsigned long interim_sent; + unsigned long interim_lost; + unsigned long interim_lost_1m; + unsigned long interim_lost_5m; + unsigned long interim_query_1m; + unsigned long interim_query_5m; + unsigned long fail_cnt; +}; + +static void rad_server_stat_get(struct rad_server_t *s, struct rad_server_stat_snapshot_t *stat) +{ + pthread_mutex_lock(&s->lock); + stat->req_cnt = s->req_cnt; + stat->queue_cnt = s->queue_cnt; + pthread_mutex_unlock(&s->lock); + + stat->auth_sent = __atomic_load_n(&s->stat.auth_sent, __ATOMIC_RELAXED); + stat->auth_lost = __atomic_load_n(&s->stat.auth_lost, __ATOMIC_RELAXED); + stat->auth_lost_1m = stat_accm_get_cnt(s->stat.auth_lost_1m); + stat->auth_lost_5m = stat_accm_get_cnt(s->stat.auth_lost_5m); + stat->auth_query_1m = stat_accm_get_avg(s->stat.auth_query_1m); + stat->auth_query_5m = stat_accm_get_avg(s->stat.auth_query_5m); + + stat->acct_sent = __atomic_load_n(&s->stat.acct_sent, __ATOMIC_RELAXED); + stat->acct_lost = __atomic_load_n(&s->stat.acct_lost, __ATOMIC_RELAXED); + stat->acct_lost_1m = stat_accm_get_cnt(s->stat.acct_lost_1m); + stat->acct_lost_5m = stat_accm_get_cnt(s->stat.acct_lost_5m); + stat->acct_query_1m = stat_accm_get_avg(s->stat.acct_query_1m); + stat->acct_query_5m = stat_accm_get_avg(s->stat.acct_query_5m); + + stat->interim_sent = __atomic_load_n(&s->stat.interim_sent, __ATOMIC_RELAXED); + stat->interim_lost = __atomic_load_n(&s->stat.interim_lost, __ATOMIC_RELAXED); + stat->interim_lost_1m = stat_accm_get_cnt(s->stat.interim_lost_1m); + stat->interim_lost_5m = stat_accm_get_cnt(s->stat.interim_lost_5m); + stat->interim_query_1m = stat_accm_get_avg(s->stat.interim_query_1m); + stat->interim_query_5m = stat_accm_get_avg(s->stat.interim_query_5m); + + stat->fail_cnt = __atomic_load_n(&s->stat.fail_cnt, __ATOMIC_RELAXED); +} + static void show_stat(struct rad_server_t *s, void *client) { char addr[INET6_ADDRSTRLEN]; // Sufficient size for both IPv4 and IPv6 addresses struct timespec ts; + struct rad_server_stat_snapshot_t stat; if (s->ipv4) { u_inet_ntoa(s->addr, addr); @@ -495,6 +657,7 @@ static void show_stat(struct rad_server_t *s, void *client) } clock_gettime(CLOCK_MONOTONIC, &ts); + rad_server_stat_get(s, &stat); cli_sendv(client, "radius(%i, %s):\r\n", s->id, addr); @@ -503,31 +666,31 @@ static void show_stat(struct rad_server_t *s, void *client) else cli_send(client, " state: active\r\n"); - cli_sendv(client, " fail count: %lu\r\n", s->stat_fail_cnt); + cli_sendv(client, " fail count: %lu\r\n", stat.fail_cnt); - cli_sendv(client, " request count: %i\r\n", s->req_cnt); - cli_sendv(client, " queue length: %i\r\n", s->queue_cnt); + cli_sendv(client, " request count: %i\r\n", stat.req_cnt); + cli_sendv(client, " queue length: %i\r\n", stat.queue_cnt); if (s->auth_port) { - cli_sendv(client, " auth sent: %lu\r\n", s->stat_auth_sent); + cli_sendv(client, " auth sent: %lu\r\n", stat.auth_sent); cli_sendv(client, " auth lost(total/5m/1m): %lu/%lu/%lu\r\n", - s->stat_auth_lost, stat_accm_get_cnt(s->stat_auth_lost_5m), stat_accm_get_cnt(s->stat_auth_lost_1m)); + stat.auth_lost, stat.auth_lost_5m, stat.auth_lost_1m); cli_sendv(client, " auth avg query time(5m/1m): %lu/%lu ms\r\n", - stat_accm_get_avg(s->stat_auth_query_5m), stat_accm_get_avg(s->stat_auth_query_1m)); + stat.auth_query_5m, stat.auth_query_1m); } if (s->acct_port) { - cli_sendv(client, " acct sent: %lu\r\n", s->stat_acct_sent); + cli_sendv(client, " acct sent: %lu\r\n", stat.acct_sent); cli_sendv(client, " acct lost(total/5m/1m): %lu/%lu/%lu\r\n", - s->stat_acct_lost, stat_accm_get_cnt(s->stat_acct_lost_5m), stat_accm_get_cnt(s->stat_acct_lost_1m)); + stat.acct_lost, stat.acct_lost_5m, stat.acct_lost_1m); cli_sendv(client, " acct avg query time(5m/1m): %lu/%lu ms\r\n", - stat_accm_get_avg(s->stat_acct_query_5m), stat_accm_get_avg(s->stat_acct_query_1m)); + stat.acct_query_5m, stat.acct_query_1m); - cli_sendv(client, " interim sent: %lu\r\n", s->stat_interim_sent); + cli_sendv(client, " interim sent: %lu\r\n", stat.interim_sent); cli_sendv(client, " interim lost(total/5m/1m): %lu/%lu/%lu\r\n", - s->stat_interim_lost, stat_accm_get_cnt(s->stat_interim_lost_5m), stat_accm_get_cnt(s->stat_interim_lost_1m)); + stat.interim_lost, stat.interim_lost_5m, stat.interim_lost_1m); cli_sendv(client, " interim avg query time(5m/1m): %lu/%lu ms\r\n", - stat_accm_get_avg(s->stat_interim_query_5m), stat_accm_get_avg(s->stat_interim_query_1m)); + stat.interim_query_5m, stat.interim_query_1m); } } @@ -544,6 +707,7 @@ static int show_stat_exec(const char *cmd, char * const *fields, int fields_cnt, static void __add_server(struct rad_server_t *s) { struct rad_server_t *s1; + char *old_secret; list_for_each_entry(s1, &serv_list, entry) { if (s1->addr == s->addr && s1->auth_port == s->auth_port && s1->acct_port == s->acct_port) { @@ -553,6 +717,13 @@ static void __add_server(struct rad_server_t *s) s1->need_free = 0; s1->bind_default = s->bind_default; strcpy(s1->bind_device, s->bind_device); + /* adopt the freshly parsed secret so changes take effect on reload */ + pthread_mutex_lock(&s1->lock); + old_secret = s1->secret; + s1->secret = s->secret; + s->secret = NULL; + pthread_mutex_unlock(&s1->lock); + _free(old_secret); _free(s); return; } @@ -565,20 +736,20 @@ static void __add_server(struct rad_server_t *s) list_add_tail(&s->entry, &serv_list); s->starting = conf_acct_on; - s->stat_auth_lost_1m = stat_accm_create(60); - s->stat_auth_lost_5m = stat_accm_create(5 * 60); - s->stat_auth_query_1m = stat_accm_create(60); - s->stat_auth_query_5m = stat_accm_create(5 * 60); + s->stat.auth_lost_1m = stat_accm_create(60); + s->stat.auth_lost_5m = stat_accm_create(5 * 60); + s->stat.auth_query_1m = stat_accm_create(60); + s->stat.auth_query_5m = stat_accm_create(5 * 60); - s->stat_acct_lost_1m = stat_accm_create(60); - s->stat_acct_lost_5m = stat_accm_create(5 * 60); - s->stat_acct_query_1m = stat_accm_create(60); - s->stat_acct_query_5m = stat_accm_create(5 * 60); + s->stat.acct_lost_1m = stat_accm_create(60); + s->stat.acct_lost_5m = stat_accm_create(5 * 60); + s->stat.acct_query_1m = stat_accm_create(60); + s->stat.acct_query_5m = stat_accm_create(5 * 60); - s->stat_interim_lost_1m = stat_accm_create(60); - s->stat_interim_lost_5m = stat_accm_create(5 * 60); - s->stat_interim_query_1m = stat_accm_create(60); - s->stat_interim_query_5m = stat_accm_create(5 * 60); + s->stat.interim_lost_1m = stat_accm_create(60); + s->stat.interim_lost_5m = stat_accm_create(5 * 60); + s->stat.interim_query_1m = stat_accm_create(60); + s->stat.interim_query_5m = stat_accm_create(5 * 60); s->ctx.close = serv_ctx_close; @@ -593,23 +764,24 @@ static void __free_server(struct rad_server_t *s) { log_debug("radius: free(%i)\n", s->id); - stat_accm_free(s->stat_auth_lost_1m); - stat_accm_free(s->stat_auth_lost_5m); - stat_accm_free(s->stat_auth_query_1m); - stat_accm_free(s->stat_auth_query_5m); + stat_accm_free(s->stat.auth_lost_1m); + stat_accm_free(s->stat.auth_lost_5m); + stat_accm_free(s->stat.auth_query_1m); + stat_accm_free(s->stat.auth_query_5m); - stat_accm_free(s->stat_acct_lost_1m); - stat_accm_free(s->stat_acct_lost_5m); - stat_accm_free(s->stat_acct_query_1m); - stat_accm_free(s->stat_acct_query_5m); + stat_accm_free(s->stat.acct_lost_1m); + stat_accm_free(s->stat.acct_lost_5m); + stat_accm_free(s->stat.acct_query_1m); + stat_accm_free(s->stat.acct_query_5m); - stat_accm_free(s->stat_interim_lost_1m); - stat_accm_free(s->stat_interim_lost_5m); - stat_accm_free(s->stat_interim_query_1m); - stat_accm_free(s->stat_interim_query_5m); + stat_accm_free(s->stat.interim_lost_1m); + stat_accm_free(s->stat.interim_lost_5m); + stat_accm_free(s->stat.interim_query_1m); + stat_accm_free(s->stat.interim_query_5m); triton_context_unregister(&s->ctx); + _free(s->secret); _free(s); } diff --git a/accel-pppd/session.c b/accel-pppd/session.c index c01417f3..c16f68ae 100644 --- a/accel-pppd/session.c +++ b/accel-pppd/session.c @@ -30,6 +30,14 @@ #define SID_SOURCE_SEQ 0 #define SID_SOURCE_URANDOM 1 +#ifndef __WORDSIZE +#if defined(__GLIBC__) || defined(__UCLIBC__) +#include <bits/wordsize.h> +#else +#include <bits/reg.h> +#endif +#endif + static int conf_sid_ucase; static int conf_single_session = -1; static int conf_single_session_ignore_case; @@ -54,13 +62,58 @@ static spinlock_t seq_lock; static long long unsigned seq; static struct timespec seq_ts; -struct ap_session_stat __export ap_session_stat; +static struct ap_session_stat ap_session_stat; static void (*shutdown_cb)(void); static void generate_sessionid(struct ap_session *ses); static void save_seq(void); +void __export ap_session_stat_get(struct ap_session_stat *stat) +{ + stat->starting = __atomic_load_n(&ap_session_stat.starting, __ATOMIC_RELAXED); + stat->active = __atomic_load_n(&ap_session_stat.active, __ATOMIC_RELAXED); + stat->finishing = __atomic_load_n(&ap_session_stat.finishing, __ATOMIC_RELAXED); +} + +unsigned int __export ap_session_stat_starting(void) +{ + return __atomic_load_n(&ap_session_stat.starting, __ATOMIC_RELAXED); +} + +unsigned int __export ap_session_stat_active(void) +{ + return __atomic_load_n(&ap_session_stat.active, __ATOMIC_RELAXED); +} + +unsigned int __export ap_session_stat_finishing(void) +{ + return __atomic_load_n(&ap_session_stat.finishing, __ATOMIC_RELAXED); +} + +static void ap_session_stat_inc(unsigned int *stat) +{ + __atomic_add_fetch(stat, 1, __ATOMIC_RELAXED); +} + +static void ap_session_stat_dec(unsigned int *stat) +{ + __atomic_sub_fetch(stat, 1, __ATOMIC_RELAXED); +} + +static void ap_session_stat_move(unsigned int *from, unsigned int *to) +{ + ap_session_stat_dec(from); + ap_session_stat_inc(to); +} + +static int ap_session_stat_idle(void) +{ + return !ap_session_stat_starting() + && !ap_session_stat_active() + && !ap_session_stat_finishing(); +} + void __export ap_session_init(struct ap_session *ses) { memset(ses, 0, sizeof(*ses)); @@ -105,7 +158,7 @@ int __export ap_session_starting(struct ap_session *ses) ses->state = AP_STATE_STARTING; } - __sync_add_and_fetch(&ap_session_stat.starting, 1); + ap_session_stat_inc(&ap_session_stat.starting); pthread_rwlock_wrlock(&ses_lock); list_add_tail(&ses->entry, &ses_list); @@ -148,8 +201,7 @@ void __export ap_session_activate(struct ap_session *ses) return; ses->state = AP_STATE_ACTIVE; - __sync_sub_and_fetch(&ap_session_stat.starting, 1); - __sync_add_and_fetch(&ap_session_stat.active, 1); + ap_session_stat_move(&ap_session_stat.starting, &ap_session_stat.active); if (!ses->session_timeout && conf_session_timeout) ses->session_timeout = conf_session_timeout; @@ -178,7 +230,6 @@ void __export ap_session_finished(struct ap_session *ses) if (!ses->down) { ap_session_ifdown(ses); - ap_session_read_stats(ses, NULL); triton_event_fire(EV_SES_FINISHING, ses); } @@ -191,14 +242,14 @@ void __export ap_session_finished(struct ap_session *ses) switch (ses->state) { case AP_STATE_ACTIVE: - __sync_sub_and_fetch(&ap_session_stat.active, 1); + ap_session_stat_dec(&ap_session_stat.active); break; case AP_STATE_RESTORE: case AP_STATE_STARTING: - __sync_sub_and_fetch(&ap_session_stat.starting, 1); + ap_session_stat_dec(&ap_session_stat.starting); break; case AP_STATE_FINISHING: - __sync_sub_and_fetch(&ap_session_stat.finishing, 1); + ap_session_stat_dec(&ap_session_stat.finishing); break; } @@ -259,7 +310,7 @@ void __export ap_session_finished(struct ap_session *ses) ses->backup->storage->free(ses->backup); #endif - if (ap_shutdown && !ap_session_stat.starting && !ap_session_stat.active && !ap_session_stat.finishing) { + if (ap_shutdown && ap_session_stat_idle()) { if (shutdown_cb) shutdown_cb(); else @@ -292,19 +343,18 @@ void __export ap_session_terminate(struct ap_session *ses, int cause, int hard) } if (ses->state == AP_STATE_ACTIVE) - __sync_sub_and_fetch(&ap_session_stat.active, 1); + ap_session_stat_move(&ap_session_stat.active, &ap_session_stat.finishing); else - __sync_sub_and_fetch(&ap_session_stat.starting, 1); + ap_session_stat_move(&ap_session_stat.starting, &ap_session_stat.finishing); - __sync_add_and_fetch(&ap_session_stat.finishing, 1); ses->terminating = 1; ses->state = AP_STATE_FINISHING; log_ppp_debug("terminate\n"); + ap_session_read_stats(ses, NULL); if (ses->ctrl->terminate(ses, hard)) { ap_session_ifdown(ses); - ap_session_read_stats(ses, NULL); triton_event_fire(EV_SES_FINISHING, ses); @@ -326,7 +376,7 @@ int ap_shutdown_soft(void (*cb)(void), int term) pthread_rwlock_rdlock(&ses_lock); - if (!ap_session_stat.starting && !ap_session_stat.active && !ap_session_stat.finishing) { + if (ap_session_stat_idle()) { pthread_rwlock_unlock(&ses_lock); if (shutdown_cb) shutdown_cb(); diff --git a/accel-pppd/session_backup.c b/accel-pppd/session_backup.c index dea5b600..2873136f 100644 --- a/accel-pppd/session_backup.c +++ b/accel-pppd/session_backup.c @@ -59,6 +59,8 @@ static int session_save(struct ap_session *ses, struct backup_mod *m) static int session_restore(struct ap_session *ses, struct backup_mod *m) { struct backup_tag *t; + time_t start_time; + uint32_t ifindex; list_for_each_entry(t, &m->tag_list, entry) { switch(t->id) { @@ -79,11 +81,14 @@ static int session_restore(struct ap_session *ses, struct backup_mod *m) ses->ifname[t->size] = 0; break; case SES_TAG_START_TIME: - ses->start_time = *(time_t *)t->data; + memcpy(&start_time, t->data, sizeof(start_time)); + ses->start_time = start_time; break; case SES_TAG_IFINDEX: - if (ses->backup->internal) - ses->ifindex = *(uint32_t *)t->data; + if (ses->backup->internal) { + memcpy(&ifindex, t->data, sizeof(ifindex)); + ses->ifindex = ifindex; + } break; /*case PPP_TAG_FD: ses->fd = *(int *)t->data; diff --git a/accel-pppd/triton/list.h b/accel-pppd/triton/list.h index 20b917d4..6352aa82 100644 --- a/accel-pppd/triton/list.h +++ b/accel-pppd/triton/list.h @@ -1,6 +1,8 @@ #ifndef _LINUX_LIST_H #define _LINUX_LIST_H +#include <stddef.h> + //#if defined(__KERNEL__) || defined(_LVM_H_INCLUDE) //#include <linux/prefetch.h> @@ -211,7 +213,7 @@ static inline void list_splice_init(struct list_head *list, * @member: the name of the list_struct within the struct. */ #define list_entry(ptr, type, member) \ - ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member))) + ((type *)((char *)(ptr)-offsetof(type, member))) /** * list_first_entry - get the first element from a list diff --git a/accel-pppd/triton/md.c b/accel-pppd/triton/md.c index 9b7dd81a..374e49a5 100644 --- a/accel-pppd/triton/md.c +++ b/accel-pppd/triton/md.c @@ -87,7 +87,7 @@ static void *md_thread(void *arg) if (!h->pending) { list_add_tail(&h->entry2, &h->ctx->pending_handlers); h->pending = 1; - __sync_add_and_fetch(&triton_stat.md_handler_pending, 1); + triton_stat_md_handler_pending_inc(); r = triton_queue_ctx(h->ctx); } else r = 0; @@ -129,7 +129,7 @@ void __export triton_md_register_handler(struct triton_context_t *ctx, struct tr list_add_tail(&h->entry, &h->ctx->handlers); spin_unlock(&h->ctx->lock); - __sync_add_and_fetch(&triton_stat.md_handler_count, 1); + triton_stat_md_handler_count_inc(); } void __export triton_md_unregister_handler(struct triton_md_handler_t *ud, int c) @@ -148,7 +148,7 @@ void __export triton_md_unregister_handler(struct triton_md_handler_t *ud, int c list_del(&h->entry); if (h->pending) { list_del(&h->entry2); - __sync_sub_and_fetch(&triton_stat.md_handler_pending, 1); + triton_stat_md_handler_pending_dec(); } spin_unlock(&h->ctx->lock); @@ -158,7 +158,7 @@ void __export triton_md_unregister_handler(struct triton_md_handler_t *ud, int c ud->tpd = NULL; - __sync_sub_and_fetch(&triton_stat.md_handler_count, 1); + triton_stat_md_handler_count_dec(); } int __export triton_md_enable_handler(struct triton_md_handler_t *ud, int mode) diff --git a/accel-pppd/triton/mempool.c b/accel-pppd/triton/mempool.c index ea6d1e6d..a3c72dae 100644 --- a/accel-pppd/triton/mempool.c +++ b/accel-pppd/triton/mempool.c @@ -95,7 +95,7 @@ void __export *mempool_alloc(mempool_t *pool) { struct _mempool_t *p = (struct _mempool_t *)pool; struct _item_t *it; - uint32_t size = sizeof(*it) + p->size + 8; + size_t size = sizeof(*it) + p->size + 8; spin_lock(&p->lock); if (!list_empty(&p->items)) { @@ -104,7 +104,7 @@ void __export *mempool_alloc(mempool_t *pool) spin_unlock(&p->lock); --p->objects; - __sync_sub_and_fetch(&triton_stat.mempool_available, size); + triton_stat_mempool_available_sub(size); return it->ptr; } @@ -121,10 +121,10 @@ void __export *mempool_alloc(mempool_t *pool) it = (struct _item_t *)mmap_ptr; mmap_ptr += size; spin_unlock(&mmap_lock); - __sync_sub_and_fetch(&triton_stat.mempool_available, size); + triton_stat_mempool_available_sub(size); } else { it = _malloc(size); - __sync_add_and_fetch(&triton_stat.mempool_allocated, size); + triton_stat_mempool_allocated_add(size); } if (!it) { @@ -140,7 +140,7 @@ void __export mempool_free(void *ptr) { struct _item_t *it = container_of(ptr, typeof(*it), ptr); struct _mempool_t *p = it->owner; - uint32_t size = sizeof(*it) + it->owner->size + 8; + size_t size = sizeof(*it) + it->owner->size + 8; int need_free = 0; #ifdef MEMDEBUG @@ -184,9 +184,9 @@ void __export mempool_free(void *ptr) #else if (need_free) { _free(it); - __sync_sub_and_fetch(&triton_stat.mempool_allocated, size); + triton_stat_mempool_allocated_sub(size); } else - __sync_add_and_fetch(&triton_stat.mempool_available, size); + triton_stat_mempool_available_add(size); #endif } @@ -220,7 +220,7 @@ static void mempool_clean(void) { struct _mempool_t *p; struct _item_t *it; - uint32_t size; + size_t size; triton_log_error("mempool: clean"); @@ -238,8 +238,8 @@ static void mempool_clean(void) #endif list_del(&it->entry); _free(it); - __sync_sub_and_fetch(&triton_stat.mempool_allocated, size); - __sync_sub_and_fetch(&triton_stat.mempool_available, size); + triton_stat_mempool_allocated_sub(size); + triton_stat_mempool_available_sub(size); #ifdef VALGRIND } else break; @@ -257,7 +257,7 @@ static void sigclean(int num) static int mmap_grow(void) { - int size = sysconf(_SC_PAGESIZE) * (1 << PAGE_ORDER); + size_t size = sysconf(_SC_PAGESIZE) * (1 << PAGE_ORDER); uint8_t *ptr; if (mmap_endptr) { @@ -275,8 +275,8 @@ static int mmap_grow(void) mmap_endptr = ptr + size; - __sync_add_and_fetch(&triton_stat.mempool_allocated, size); - __sync_add_and_fetch(&triton_stat.mempool_available, size); + triton_stat_mempool_allocated_add(size); + triton_stat_mempool_available_add(size); return 0; oom: @@ -301,4 +301,3 @@ static void __init init(void) mmap_grow(); } - diff --git a/accel-pppd/triton/mempool.h b/accel-pppd/triton/mempool.h index 9ad2c370..c4f27588 100644 --- a/accel-pppd/triton/mempool.h +++ b/accel-pppd/triton/mempool.h @@ -3,16 +3,10 @@ #include <stdint.h> -struct mempool_stat_t -{ - uint32_t allocated; - uint32_t available; -}; typedef void * mempool_t; mempool_t *mempool_create(int size); mempool_t *mempool_create2(int size); -struct mempool_stat_t mempool_get_stat(void); #ifdef MEMDEBUG #include "memdebug.h" diff --git a/accel-pppd/triton/timer.c b/accel-pppd/triton/timer.c index 5b5d9535..ed23eb1d 100644 --- a/accel-pppd/triton/timer.c +++ b/accel-pppd/triton/timer.c @@ -95,7 +95,7 @@ void *timer_thread(void *arg) if (!t->pending) { list_add_tail(&t->entry2, &t->ctx->pending_timers); t->pending = 1; - __sync_add_and_fetch(&triton_stat.timer_pending, 1); + triton_stat_timer_pending_inc(); r = triton_queue_ctx(t->ctx); } else r = 0; @@ -167,7 +167,7 @@ int __export triton_timer_add(struct triton_context_t *ctx, struct triton_timer_ goto out_err; } - __sync_add_and_fetch(&triton_stat.timer_count, 1); + triton_stat_timer_count_inc(); return 0; @@ -206,7 +206,7 @@ void __export triton_timer_del(struct triton_timer_t *ud) list_del(&t->entry); if (t->pending) { list_del(&t->entry2); - __sync_sub_and_fetch(&triton_stat.timer_pending, 1); + triton_stat_timer_pending_dec(); } spin_unlock(&t->ctx->lock); @@ -216,6 +216,6 @@ void __export triton_timer_del(struct triton_timer_t *ud) ud->tpd = NULL; - __sync_sub_and_fetch(&triton_stat.timer_count, 1); + triton_stat_timer_count_dec(); } diff --git a/accel-pppd/triton/triton.c b/accel-pppd/triton/triton.c index 395a42df..105f810a 100644 --- a/accel-pppd/triton/triton.c +++ b/accel-pppd/triton/triton.c @@ -32,13 +32,15 @@ static LIST_HEAD(init_list); static int terminate; static int need_terminate; +/* 0 - idle, 1 - reload requested, 2 - reload running */ static int need_config_reload; -static void (*config_reload_notify)(int); +static void (*config_reload_notify)(int, void *); +static void *config_reload_arg; static mempool_t *ctx_pool; static mempool_t *call_pool; -struct triton_stat_t __export triton_stat; +static struct triton_stat_t triton_stat; static struct timeval ru_utime; static struct timeval ru_stime; @@ -57,20 +59,167 @@ static __thread void *thread_frame; #define log_debug2(fmt, ...) +void __export triton_stat_get(struct triton_stat_t *stat) +{ + stat->mempool_allocated = __atomic_load_n(&triton_stat.mempool_allocated, __ATOMIC_RELAXED); + stat->mempool_available = __atomic_load_n(&triton_stat.mempool_available, __ATOMIC_RELAXED); + stat->thread_count = __atomic_load_n(&triton_stat.thread_count, __ATOMIC_RELAXED); + stat->thread_active = __atomic_load_n(&triton_stat.thread_active, __ATOMIC_RELAXED); + stat->context_count = __atomic_load_n(&triton_stat.context_count, __ATOMIC_RELAXED); + stat->context_sleeping = __atomic_load_n(&triton_stat.context_sleeping, __ATOMIC_RELAXED); + stat->context_pending = __atomic_load_n(&triton_stat.context_pending, __ATOMIC_RELAXED); + stat->md_handler_count = __atomic_load_n(&triton_stat.md_handler_count, __ATOMIC_RELAXED); + stat->md_handler_pending = __atomic_load_n(&triton_stat.md_handler_pending, __ATOMIC_RELAXED); + stat->timer_count = __atomic_load_n(&triton_stat.timer_count, __ATOMIC_RELAXED); + stat->timer_pending = __atomic_load_n(&triton_stat.timer_pending, __ATOMIC_RELAXED); + stat->start_time = __atomic_load_n(&triton_stat.start_time, __ATOMIC_RELAXED); + stat->cpu = __atomic_load_n(&triton_stat.cpu, __ATOMIC_RELAXED); +} + +time_t __export triton_stat_start_time(void) +{ + return __atomic_load_n(&triton_stat.start_time, __ATOMIC_RELAXED); +} + +unsigned int __export triton_stat_cpu(void) +{ + return __atomic_load_n(&triton_stat.cpu, __ATOMIC_RELAXED); +} + +void triton_stat_mempool_allocated_add(uint64_t value) +{ + __atomic_add_fetch(&triton_stat.mempool_allocated, value, __ATOMIC_RELAXED); +} + +void triton_stat_mempool_allocated_sub(uint64_t value) +{ + __atomic_sub_fetch(&triton_stat.mempool_allocated, value, __ATOMIC_RELAXED); +} + +void triton_stat_mempool_available_add(uint64_t value) +{ + __atomic_add_fetch(&triton_stat.mempool_available, value, __ATOMIC_RELAXED); +} + +void triton_stat_mempool_available_sub(uint64_t value) +{ + __atomic_sub_fetch(&triton_stat.mempool_available, value, __ATOMIC_RELAXED); +} + +void triton_stat_thread_count_inc(void) +{ + __atomic_add_fetch(&triton_stat.thread_count, 1, __ATOMIC_RELAXED); +} + +void triton_stat_thread_active_inc(void) +{ + __atomic_add_fetch(&triton_stat.thread_active, 1, __ATOMIC_RELAXED); +} + +unsigned int triton_stat_thread_active_dec(void) +{ + return __atomic_sub_fetch(&triton_stat.thread_active, 1, __ATOMIC_RELAXED); +} + +static unsigned int triton_stat_thread_active(void) +{ + return __atomic_load_n(&triton_stat.thread_active, __ATOMIC_RELAXED); +} + +void triton_stat_context_count_inc(void) +{ + __atomic_add_fetch(&triton_stat.context_count, 1, __ATOMIC_RELAXED); +} + +unsigned int triton_stat_context_count_dec(void) +{ + return __atomic_sub_fetch(&triton_stat.context_count, 1, __ATOMIC_RELAXED); +} + +void triton_stat_context_sleeping_inc(void) +{ + __atomic_add_fetch(&triton_stat.context_sleeping, 1, __ATOMIC_RELAXED); +} + +void triton_stat_context_sleeping_dec(void) +{ + __atomic_sub_fetch(&triton_stat.context_sleeping, 1, __ATOMIC_RELAXED); +} + +void triton_stat_context_pending_inc(void) +{ + __atomic_add_fetch(&triton_stat.context_pending, 1, __ATOMIC_RELAXED); +} + +void triton_stat_context_pending_dec(void) +{ + __atomic_sub_fetch(&triton_stat.context_pending, 1, __ATOMIC_RELAXED); +} + +void triton_stat_md_handler_count_inc(void) +{ + __atomic_add_fetch(&triton_stat.md_handler_count, 1, __ATOMIC_RELAXED); +} + +void triton_stat_md_handler_count_dec(void) +{ + __atomic_sub_fetch(&triton_stat.md_handler_count, 1, __ATOMIC_RELAXED); +} + +void triton_stat_md_handler_pending_inc(void) +{ + __atomic_add_fetch(&triton_stat.md_handler_pending, 1, __ATOMIC_RELAXED); +} + +void triton_stat_md_handler_pending_dec(void) +{ + __atomic_sub_fetch(&triton_stat.md_handler_pending, 1, __ATOMIC_RELAXED); +} + +void triton_stat_timer_count_inc(void) +{ + __atomic_add_fetch(&triton_stat.timer_count, 1, __ATOMIC_RELAXED); +} + +void triton_stat_timer_count_dec(void) +{ + __atomic_sub_fetch(&triton_stat.timer_count, 1, __ATOMIC_RELAXED); +} + +void triton_stat_timer_pending_inc(void) +{ + __atomic_add_fetch(&triton_stat.timer_pending, 1, __ATOMIC_RELAXED); +} + +void triton_stat_timer_pending_dec(void) +{ + __atomic_sub_fetch(&triton_stat.timer_pending, 1, __ATOMIC_RELAXED); +} + +void triton_stat_set_cpu(unsigned int value) +{ + __atomic_store_n(&triton_stat.cpu, value, __ATOMIC_RELAXED); +} + +void triton_stat_set_start_time(time_t value) +{ + __atomic_store_n(&triton_stat.start_time, value, __ATOMIC_RELAXED); +} + void triton_thread_wakeup(struct _triton_thread_t *thread) { log_debug2("wake up thread %p\n", thread); pthread_kill(thread->thread, SIGUSR1); } -static void __config_reload(void (*notify)(int)) +static void __config_reload(void) { struct _triton_thread_t *t; int r; log_debug2("config_reload: enter\n"); r = conf_reload(NULL); - notify(r); + config_reload_notify(r, config_reload_arg); spin_lock(&threads_lock); need_config_reload = 0; @@ -146,7 +295,7 @@ static void* triton_thread(struct _triton_thread_t *thread) thread->ctx->thread = thread; thread->ctx->queued = 0; spin_unlock(&threads_lock); - __sync_sub_and_fetch(&triton_stat.context_pending, 1); + triton_stat_context_pending_dec(); } } else { log_debug2("thread: %p: sleeping\n", thread); @@ -154,9 +303,10 @@ static void* triton_thread(struct _triton_thread_t *thread) if (!terminate) list_add(&thread->entry2, &sleep_threads); - if (__sync_sub_and_fetch(&triton_stat.thread_active, 1) == 0 && need_config_reload) { + if (triton_stat_thread_active_dec() == 0 && need_config_reload == 1) { + need_config_reload = 2; spin_unlock(&threads_lock); - __config_reload(config_reload_notify); + __config_reload(); } else spin_unlock(&threads_lock); @@ -172,7 +322,7 @@ static void* triton_thread(struct _triton_thread_t *thread) //printf("thread %p: exit sigwait\n", thread); spin_lock(&threads_lock); - __sync_add_and_fetch(&triton_stat.thread_active, 1); + triton_stat_thread_active_inc(); if (!thread->ctx) { list_del(&thread->entry2); spin_unlock(&threads_lock); @@ -228,7 +378,7 @@ static void ctx_thread(struct _triton_context_t *ctx) list_del(&t->entry2); t->pending = 0; spin_unlock(&ctx->lock); - __sync_sub_and_fetch(&triton_stat.timer_pending, 1); + triton_stat_timer_pending_dec(); read(t->fd, &tt, sizeof(tt)); if (t->ud) t->ud->expire(t->ud); @@ -243,7 +393,7 @@ static void ctx_thread(struct _triton_context_t *ctx) h->trig_epoll_events = 0; spin_unlock(&ctx->lock); - __sync_sub_and_fetch(&triton_stat.md_handler_pending, 1); + triton_stat_md_handler_pending_dec(); h->armed = 0; @@ -320,8 +470,8 @@ struct _triton_thread_t *create_thread() while (pthread_create(&thread->thread, &attr, (void*(*)(void*))triton_thread, thread)) sleep(1); - __sync_add_and_fetch(&triton_stat.thread_count, 1); - __sync_add_and_fetch(&triton_stat.thread_active, 1); + triton_stat_thread_count_inc(); + triton_stat_thread_active_inc(); return thread; } @@ -340,7 +490,7 @@ int triton_queue_ctx(struct _triton_context_t *ctx) spin_unlock(&threads_lock); ctx->queued = 1; log_debug2("ctx %p: queued\n", ctx); - __sync_add_and_fetch(&triton_stat.context_pending, 1); + triton_stat_context_pending_inc(); return 0; } @@ -386,8 +536,8 @@ int __export triton_context_register(struct triton_context_t *ud, void *bf_arg) list_add_tail(&ctx->entry, &ctx_list); spin_unlock(&ctx_list_lock); - __sync_add_and_fetch(&triton_stat.context_sleeping, 1); - __sync_add_and_fetch(&triton_stat.context_count, 1); + triton_stat_context_sleeping_inc(); + triton_stat_context_count_inc(); return 0; } @@ -437,7 +587,7 @@ void __export triton_context_unregister(struct triton_context_t *ud) spin_lock(&ctx_list_lock); list_del(&ctx->entry); - if (__sync_sub_and_fetch(&triton_stat.context_count, 1) == 1) { + if (triton_stat_context_count_dec() == 1) { if (need_terminate) terminate = 1; } @@ -492,7 +642,7 @@ void __export triton_context_schedule() volatile struct _triton_context_t *ctx = (struct _triton_context_t *)this_ctx->tpd; log_debug2("ctx %p: enter schedule\n", ctx); - __sync_add_and_fetch(&triton_stat.context_sleeping, 1); + triton_stat_context_sleeping_inc(); ctx->uc = alloc_context(); @@ -509,7 +659,7 @@ void __export triton_context_schedule() spin_unlock(&threads_lock); _free(ctx->uc); ctx->uc = NULL; - __sync_sub_and_fetch(&triton_stat.context_sleeping, 1); + triton_stat_context_sleeping_dec(); log_debug2("ctx %p: exit schedule\n", ctx); } else { ctx->asleep = 1; @@ -527,7 +677,7 @@ void __export triton_context_wakeup(struct triton_context_t *ud) log_debug2("ctx %p: wakeup\n", ctx); if (ctx->init) { - __sync_sub_and_fetch(&triton_stat.context_sleeping, 1); + triton_stat_context_sleeping_dec(); spin_lock(&ctx->lock); ctx->init = 0; if (ctx->pending) @@ -610,7 +760,7 @@ void __export triton_collect_cpu_usage(void) clock_gettime(CLOCK_MONOTONIC, &ru_timestamp); ru_utime = rusage.ru_utime; ru_stime = rusage.ru_stime; - triton_stat.cpu = 0; + triton_stat_set_cpu(0); } } @@ -634,7 +784,7 @@ static void ru_update(struct triton_timer_t *t) val = (double)((rusage.ru_utime.tv_sec - ru_utime.tv_sec) * 1000000 + (rusage.ru_utime.tv_usec - ru_utime.tv_usec) + (rusage.ru_stime.tv_sec - ru_stime.tv_sec) * 1000000 + (rusage.ru_stime.tv_usec - ru_stime.tv_usec)) / dt * 100; - triton_stat.cpu = val; + triton_stat_set_cpu(val); ru_timestamp = ts; ru_utime = rusage.ru_utime; @@ -709,16 +859,25 @@ int __export triton_load_modules(const char *mod_sect) return 0; } -void __export triton_conf_reload(void (*notify)(int)) +int __export triton_conf_reload(void (*notify)(int, void *), void *arg) { spin_lock(&threads_lock); - need_config_reload = 1; + if (need_config_reload) { + spin_unlock(&threads_lock); + return -1; + } config_reload_notify = notify; - if (triton_stat.thread_active == 0) { + config_reload_arg = arg; + if (triton_stat_thread_active() == 0) { + need_config_reload = 2; spin_unlock(&threads_lock); - __config_reload(notify); - } else + __config_reload(); + } else { + need_config_reload = 1; spin_unlock(&threads_lock); + } + + return 0; } void __export triton_run() @@ -752,7 +911,7 @@ void __export triton_run() } clock_gettime(CLOCK_MONOTONIC, &ts); - triton_stat.start_time = ts.tv_sec; + triton_stat_set_start_time(ts.tv_sec); md_run(); timer_run(); diff --git a/accel-pppd/triton/triton.h b/accel-pppd/triton/triton.h index 79bbee1b..63dc9188 100644 --- a/accel-pppd/triton/triton.h +++ b/accel-pppd/triton/triton.h @@ -55,8 +55,8 @@ struct conf_sect_t struct triton_stat_t { - unsigned int mempool_allocated; - unsigned int mempool_available; + uint64_t mempool_allocated; + uint64_t mempool_available; unsigned int thread_count; unsigned int thread_active; unsigned int context_count; @@ -70,7 +70,9 @@ struct triton_stat_t unsigned int cpu; }; -extern struct triton_stat_t triton_stat; +void triton_stat_get(struct triton_stat_t *stat); +time_t triton_stat_start_time(void); +unsigned int triton_stat_cpu(void); int triton_context_register(struct triton_context_t *, void *arg); void triton_context_unregister(struct triton_context_t *); void triton_context_set_priority(struct triton_context_t *, int); @@ -102,7 +104,7 @@ void triton_event_fire(int ev_id, void *arg); struct conf_sect_t *conf_get_section(const char *name); char *conf_get_opt(const char *sect, const char *name); -void triton_conf_reload(void (*notify)(int)); +int triton_conf_reload(void (*notify)(int, void *), void *arg); void triton_collect_cpu_usage(void); void triton_stop_collect_cpu_usage(void); diff --git a/accel-pppd/triton/triton_p.h b/accel-pppd/triton/triton_p.h index bc39d626..7fa70b63 100644 --- a/accel-pppd/triton/triton_p.h +++ b/accel-pppd/triton/triton_p.h @@ -119,4 +119,28 @@ void triton_log_debug(const char *fmt, ...) __attribute__((format(gnu_printf, 1, int load_modules(const char *name); void triton_context_release(struct _triton_context_t *ctx); +void triton_stat_mempool_allocated_add(uint64_t value); +void triton_stat_mempool_allocated_sub(uint64_t value); +void triton_stat_mempool_available_add(uint64_t value); +void triton_stat_mempool_available_sub(uint64_t value); +void triton_stat_thread_count_inc(void); +void triton_stat_thread_active_inc(void); +unsigned int triton_stat_thread_active_dec(void); +void triton_stat_context_count_inc(void); +unsigned int triton_stat_context_count_dec(void); +void triton_stat_context_sleeping_inc(void); +void triton_stat_context_sleeping_dec(void); +void triton_stat_context_pending_inc(void); +void triton_stat_context_pending_dec(void); +void triton_stat_md_handler_count_inc(void); +void triton_stat_md_handler_count_dec(void); +void triton_stat_md_handler_pending_inc(void); +void triton_stat_md_handler_pending_dec(void); +void triton_stat_timer_count_inc(void); +void triton_stat_timer_count_dec(void); +void triton_stat_timer_pending_inc(void); +void triton_stat_timer_pending_dec(void); +void triton_stat_set_cpu(unsigned int value); +void triton_stat_set_start_time(time_t value); + #endif diff --git a/accel-pppd/utils.c b/accel-pppd/utils.c index 018b6efa..f92ebe19 100644 --- a/accel-pppd/utils.c +++ b/accel-pppd/utils.c @@ -347,3 +347,14 @@ int __export u_randbuf(void *buf, size_t buf_len, int *err) return 0; } + +void __export u_strstrip(char *str, char c) +{ + char *src = str, *dst = str; + while (*src) { + if (*src != c) + *dst++ = *src; + src++; + } + *dst = '\0'; +} diff --git a/accel-pppd/utils.h b/accel-pppd/utils.h index 06859a6b..6c03299f 100644 --- a/accel-pppd/utils.h +++ b/accel-pppd/utils.h @@ -1,8 +1,89 @@ #ifndef __UTILS_H #define __UTILS_H +#include <endian.h> #include <netinet/in.h> #include <stdint.h> +#include <string.h> + +#ifndef min +#define min(x, y) ((x) < (y) ? (x) : (y)) +#endif + +#ifndef max +#define max(x, y) ((x) > (y) ? (x) : (y)) +#endif + +/* + * Fixed-size memcpy() lets the compiler emit efficient unaligned accesses on + * architectures that support them without imposing alignment or aliasing + * requirements on callers. + */ +static inline uint16_t u_read_be16(const void *ptr) +{ + uint16_t value; + + memcpy(&value, ptr, sizeof(value)); + return ntohs(value); +} + +static inline uint32_t u_read_be32(const void *ptr) +{ + uint32_t value; + + memcpy(&value, ptr, sizeof(value)); + return ntohl(value); +} + +static inline uint64_t u_read_be64(const void *ptr) +{ + uint64_t value; + + memcpy(&value, ptr, sizeof(value)); + return be64toh(value); +} + +static inline void u_write_be16(void *ptr, uint16_t value) +{ + value = htons(value); + memcpy(ptr, &value, sizeof(value)); +} + +static inline void u_write_be32(void *ptr, uint32_t value) +{ + value = htonl(value); + memcpy(ptr, &value, sizeof(value)); +} + +static inline void u_write_be64(void *ptr, uint64_t value) +{ + value = htobe64(value); + memcpy(ptr, &value, sizeof(value)); +} + +static inline uint16_t u_read_native16(const void *ptr) +{ + uint16_t value; + + memcpy(&value, ptr, sizeof(value)); + return value; +} + +static inline uint32_t u_read_native32(const void *ptr) +{ + uint32_t value; + + memcpy(&value, ptr, sizeof(value)); + return value; +} + +static inline uint64_t u_read_native64(const void *ptr) +{ + uint64_t value; + + memcpy(&value, ptr, sizeof(value)); + return value; +} char *u_ip6str(const struct in6_addr *addr, char *buf); char *u_ip4str(const struct in_addr *addr, char *buf); @@ -24,6 +105,7 @@ size_t u_parse_ip6cidr(const char *str, struct in6_addr *netp, uint8_t *plen); size_t u_parse_ip4cidr(const char *str, struct in_addr *netp, uint8_t *plen); size_t u_parse_ip4range(const char *str, struct in_addr *base_ip, uint8_t *max); +void u_strstrip(char *str, char c); int u_randbuf(void *buf, size_t buf_len, int *err); #endif |
