Lines 1-6
Link Here
|
1 |
# Copyright (c) 2016 Emeric Verschuur <emeric@mbedsys.org> |
1 |
# Copyright (c) 2016 Emeric Verschuur <emeric@mbedsys.org> |
|
|
2 |
# Copyright (c) 2023 Kerin Millar <kfm@plushkava.net> |
2 |
# All rights reserved. Released under the 2-clause BSD license. |
3 |
# All rights reserved. Released under the 2-clause BSD license. |
3 |
# shellcheck shell=sh disable=SC1008 |
4 |
|
|
|
5 |
# Don't complain about local, even though POSIX does not define its behaviour. |
6 |
# This is unwise but, as things stand, it is being used extensively by netifrc. |
7 |
# Also, SC2034 and SC2316 are muted because they produce false-positives. |
8 |
# shellcheck shell=sh disable=SC3043,SC2034,SC2316 |
4 |
|
9 |
|
5 |
l2tp_depend() |
10 |
l2tp_depend() |
6 |
{ |
11 |
{ |
Lines 8-173
l2tp_depend()
Link Here
|
8 |
before bridge interface macchanger |
13 |
before bridge interface macchanger |
9 |
} |
14 |
} |
10 |
|
15 |
|
11 |
# Extract parameter list to shell vars |
16 |
_l2tp_parse_opts() |
12 |
# 1. variable prefix |
17 |
{ |
13 |
# 2. string to parse |
18 |
# Parses lt2psession or l2tptunnel options using xargs(1), conveying |
14 |
_l2tp_eval_props() { |
19 |
# them as arguments to awk(1). The awk program interprets the arguments |
15 |
local prop_pref=$1 |
20 |
# as a series of key/value pairs and safely prints those specified as |
16 |
local prop_list=$2 |
21 |
# being required as variable declarations for evaluation by sh(1). |
17 |
eval set -- "$3" |
22 |
# Other keys are handled similarly, only in a way that renders them a |
18 |
while [ -n "$1" ]; do |
23 |
# no-op. For the program to exit successfully, all key names must be |
19 |
eval "case $1 in |
24 |
# well-formed, all required keys must be seen, and all values must be |
20 |
$prop_list) |
25 |
# non-blank. Note that assigning 1 to ARGC prevents awk from treating |
21 |
$prop_pref$1=\"$2\" |
26 |
# its arguments as the names of files to be opened. |
22 |
shift |
27 |
printf %s "$1" \ |
23 |
shift |
28 |
| LC_CTYPE=C xargs -E '' awk -v q="'" -v required_keys="$2" -v other_keys="$3" ' |
24 |
;; |
29 |
function shquote(str) { |
25 |
*) |
30 |
gsub(q, q "\\" q q, str) |
26 |
l2tp_err=\"invalid property $1\" |
31 |
return q str q |
27 |
return 1 |
32 |
} |
28 |
;; |
33 |
BEGIN { |
29 |
|
34 |
argc = ARGC |
30 |
esac" || return 1 |
35 |
ARGC = 1 |
31 |
done |
36 |
gsub(" ", "|", required_keys) |
32 |
return 0 |
37 |
gsub(" ", "|", other_keys) |
|
|
38 |
re = "^(" required_keys "|" other_keys ")$" |
39 |
sorter = "sort" |
40 |
for (i = 1; i < argc; i += 2) { |
41 |
key = ARGV[i] |
42 |
val = ARGV[i + 1] |
43 |
if (key !~ /^[[:alpha:]][_[:alnum:]]+$/) { |
44 |
system("ewarn " shquote("Skipping malformed parameter: " key)) |
45 |
} else if (key ~ re) { |
46 |
print key "=" shquote(val) | sorter |
47 |
val_by[key] = val |
48 |
} else { |
49 |
print ": " key "=" shquote(val) | sorter |
50 |
} |
51 |
} |
52 |
close(sorter) |
53 |
split(required_keys, keys, "|") |
54 |
missing = 0 |
55 |
for (i in keys) { |
56 |
key = keys[i] |
57 |
if (! (key in val_by)) { |
58 |
system("eerror " shquote("The \"" key "\" parameter is missing")) |
59 |
missing += 1 |
60 |
} else if (val_by[key] ~ /^[[:blank:]]*$/) { |
61 |
system("eerror " shquote("The \"" key "\" parameter has a blank value")) |
62 |
missing += 1 |
63 |
} |
64 |
} |
65 |
exit(!!missing) |
66 |
} |
67 |
' |
33 |
} |
68 |
} |
34 |
|
69 |
|
35 |
_is_l2tp() { |
70 |
_l2tp_parse_existing_session() { |
36 |
# Check for L2TP support in kernel |
71 |
ip l2tp show session \ |
37 |
ip l2tp show session 2>/dev/null 1>/dev/null || return 1 |
72 |
| LC_CTYPE=C awk -v iface="${IFACE:?}" ' |
38 |
|
73 |
BEGIN { found = 0 } |
39 |
eval "$(ip l2tp show session | \ |
74 |
/^Session [0-9]+ in tunnel [0-9]+$/ { |
40 |
awk "match(\$0, /^Session ([0-9]+) in tunnel ([0-9]+)\$/, ret) {sid=ret[1]; tid=ret[2]} |
75 |
session_id = $2 |
41 |
match(\$0, /^[ ]*interface name: ${IFACE}\$/) {print \"session_id=\"sid\";tunnel_id=\"tid; exit}")" |
76 |
tunnel_id = $5 |
42 |
test -n "$session_id" |
77 |
} |
|
|
78 |
/^[[:blank:]]*interface name:/ && "" $NF == "" iface { |
79 |
print "session_id=" session_id |
80 |
print "tunnel_id=" tunnel_id |
81 |
found = 1 |
82 |
exit |
83 |
} |
84 |
END { exit(!found) } |
85 |
' |
43 |
} |
86 |
} |
44 |
|
87 |
|
45 |
# Get tunnel info |
88 |
|
46 |
# 1. Output variable prefix |
89 |
_l2tp_parse_existing_tunnel() { |
47 |
# 2. Tunnel ID to find |
90 |
ip l2tp show tunnel \ |
48 |
_l2tp_get_tunnel_info() { |
91 |
| LC_CTYPE=C awk -v q="'" -v id="$1" ' |
49 |
local found |
92 |
function shquote(str) { |
50 |
eval "$(ip l2tp show tunnel | \ |
93 |
gsub(q, q "\\" q q, str) |
51 |
awk -v id=$2 -v prefix=$1 ' |
94 |
return q str q |
52 |
match($0, /^Tunnel ([0-9]+), encap (IP|UDP)$/, ret) { |
|
|
53 |
if (found == "1") exit; |
54 |
if (ret[1] == id) { |
55 |
print "found=1;" |
56 |
print prefix "tunnel_id=" ret[1] ";" |
57 |
print prefix "encap=" ret[2] ";"; |
58 |
found="1" |
59 |
} |
60 |
} |
61 |
match($0, /^[ ]*From ([^ ]+) to ([^ ]+)$/, ret) { |
62 |
if (found == "1") { |
63 |
print prefix "local=" ret[1] ";"; |
64 |
print prefix "remote=" ret[2] ";"; |
65 |
} |
66 |
} |
95 |
} |
67 |
match($0, /^[ ]*Peer tunnel ([0-9]+)$/, ret) { |
96 |
BEGIN { |
68 |
if (found == "1") { |
97 |
found = 0 |
69 |
print prefix "peer_tunnel_id=" ret[1] ";"; |
98 |
sorter = "sort" |
70 |
} |
|
|
71 |
} |
99 |
} |
72 |
match($0, /^[ ]*UDP source \/ dest ports: ([0-9]+)\/([0-9]+)$/, ret) { |
100 |
/^Tunnel [0-9]+, encap (IP|UDP)$/ { |
73 |
if (found == "1") { |
101 |
if (found) exit |
74 |
print prefix "udp_sport=" ret[1] ";"; |
102 |
tunnel_id = substr($2, 0, length($2) - 1) |
75 |
print prefix "udp_dport=" ret[2] ";"; |
103 |
if (tunnel_id == id) { |
|
|
104 |
found = 1 |
105 |
print "tunnel_id=" shquote(tunnel_id) | sorter |
106 |
print "encap=" shquote(tolower($4)) | sorter |
76 |
} |
107 |
} |
77 |
}')" |
108 |
} |
78 |
test -n "$found" |
109 |
found && /^[[:blank:]]*From [^[:blank:]]+ to [^[:blank:]]+$/ { |
|
|
110 |
print "local=" shquote($2) | sorter |
111 |
print "remote=" shquote($4) | sorter |
112 |
} |
113 |
found && /^[[:blank:]]*Peer tunnel [0-9]+$/ { |
114 |
print "peer_tunnel_id=" shquote($NF) | sorter |
115 |
} |
116 |
found && /^[[:blank:]]*UDP source \/ dest ports: [0-9]+\/[0-9]+$/ { |
117 |
split($NF, ports, "/") |
118 |
print ": udp_sport=" shquote(ports[1]) | sorter |
119 |
print ": udp_dport=" shquote(ports[2]) | sorter |
120 |
} |
121 |
END { |
122 |
close(sorter) |
123 |
exit(!found) |
124 |
} |
125 |
' |
79 |
} |
126 |
} |
80 |
|
127 |
|
81 |
_ip_l2tp_add() { |
128 |
_l2tp_should_add_tunnel() { |
82 |
local e |
129 |
local existing_tunnel |
83 |
e="$(LC_ALL=C ip l2tp add "$@" 2>&1 1>/dev/null)" |
130 |
|
84 |
case $e in |
131 |
if ! existing_tunnel=$(_l2tp_parse_existing_tunnel "$1"); then |
85 |
"") |
132 |
return 0 |
86 |
return 0 |
133 |
elif [ "$2" = "${existing_tunnel}" ]; then |
87 |
;; |
134 |
return 1 |
88 |
"RTNETLINK answers: No such process") |
135 |
else |
89 |
# seems to not be a fatal error but I don't know why I have this error... hmmm |
136 |
return 2 |
90 |
ewarn "ip l2tp add $2 error: $e" |
137 |
fi |
91 |
return 0 |
|
|
92 |
;; |
93 |
*) |
94 |
eend 1 "ip l2tp add $2 error: $e" |
95 |
return 1 |
96 |
;; |
97 |
esac |
98 |
|
99 |
} |
138 |
} |
100 |
|
139 |
|
|
|
140 |
_l2tp_has_tunnel() { |
141 |
_l2tp_parse_existing_tunnel "$1" >/dev/null |
142 |
} |
143 |
|
144 |
_l2tp_in_session() { |
145 |
ip l2tp show session | { |
146 |
LC_CTYPE=C |
147 |
while read -r line; do |
148 |
case ${line} in |
149 |
"Session "*" in tunnel $1") return 0 |
150 |
esac |
151 |
done |
152 |
} |
153 |
return 1 |
154 |
} |
155 |
|
156 |
_is_blank() ( |
157 |
LC_CTYPE=C |
158 |
case $1 in |
159 |
*[![:blank:]]*) return 1 |
160 |
esac |
161 |
) |
162 |
|
101 |
l2tp_pre_start() |
163 |
l2tp_pre_start() |
102 |
{ |
164 |
{ |
103 |
local l2tpsession= |
165 |
local declared_session declared_tunnel l2tpsession l2tptunnel |
104 |
eval l2tpsession=\$l2tpsession_${IFVAR} |
166 |
local name peer_session_id session_id tunnel_id |
105 |
test -n "${l2tpsession}" || return 0 |
167 |
local encap local peer_tunnel_id remote |
106 |
|
168 |
local key |
107 |
ebegin "Creating L2TPv3 link ${IFVAR}" |
169 |
|
108 |
local l2tp_err s_name s_tunnel_id s_session_id s_peer_session_id s_cookie s_peer_cookie s_offset s_peer_offset s_l2spec_type |
170 |
if key="l2tpsession_${IFVAR:?}"; ! eval "[ \${${key}+set} ]"; then |
109 |
if ! _l2tp_eval_props s_ "name|tunnel_id|session_id|peer_session_id|cookie|peer_cookie|offset|peer_offset|l2spec_type" "${l2tpsession}"; then |
171 |
return |
110 |
eend 1 "l2tpsession_${IFVAR} syntax error: $l2tp_err" |
172 |
elif eval "l2tpsession=\$${key}"; _is_blank "${l2tpsession}"; then |
111 |
return 1 |
173 |
eend 1 "${key} is defined but its value is blank" |
112 |
fi |
174 |
elif ! declared_session=$(_l2tp_parse_opts "${l2tpsession}" "peer_session_id session_id tunnel_id" "name"); then |
113 |
if [ -n "$s_name" ]; then |
175 |
eend 1 "${key} is missing at least one required parameter" |
114 |
eend 1 "l2tpsession_${IFVAR} error: please remove the \"name\" parameter (this parameter is managed by the system)" |
176 |
elif eval "${declared_session}"; [ "${name+set}" ]; then |
115 |
return 1 |
177 |
eend 1 "${key} defines a \"name\" parameter, which is forbidden by netifrc" |
116 |
fi |
178 |
elif ! modprobe l2tp_eth; then |
117 |
# Try to load mendatory l2tp_eth kernel module |
179 |
eend 1 "Couldn't load the l2tp_eth module (perhaps the CONFIG_L2TP_ETH kernel option is disabled)" |
118 |
if ! modprobe l2tp_eth; then |
180 |
elif key="l2tptunnel_${IFVAR}"; eval "[ \${${key}+set} ]"; then |
119 |
eend 1 "l2tp_eth module not present in your kernel (please enable CONFIG_L2TP_ETH option in your kernel config)" |
181 |
if eval "l2tptunnel=\$${key}"; _is_blank "${l2tptunnel}"; then |
120 |
return 1 |
182 |
eend 1 "${key} is defined but its value is blank" |
121 |
fi |
183 |
elif ! declared_tunnel=$(_l2tp_parse_opts "${l2tptunnel}" "local peer_tunnel_id remote tunnel_id" "encap"); then |
122 |
local l2tptunnel= |
184 |
eend 1 "${key} is missing at least one required parameter" |
123 |
eval l2tptunnel=\$l2tptunnel_${IFVAR} |
185 |
elif set -- "${tunnel_id}"; eval "${declared_tunnel}"; [ "$1" != "${tunnel_id}" ]; then |
124 |
if [ -n "${l2tptunnel}" ]; then |
186 |
eend 1 "${key} defines a \"tunnel_id\" parameter that contradicts l2tpsession_${IFVAR}" |
125 |
local t_tunnel_id t_encap t_local t_remote t_peer_tunnel_id t_udp_sport t_udp_dport |
187 |
elif _l2tp_should_add_tunnel "${tunnel_id}" "${declared_tunnel}"; set -- $?; [ "$1" -eq 2 ]; then |
126 |
_l2tp_eval_props t_ "remote|local|encap|tunnel_id|peer_tunnel_id|encap|udp_sport|udp_dport" "${l2tptunnel}" |
188 |
eend 1 "Tunnel #${tunnel_id} exists but its properties mismatch those defined by ${key}" |
127 |
# if encap=ip we need l2tp_ip kernel module |
189 |
elif [ "$1" -eq 1 ]; then |
128 |
if [ "${t_encap^^}" = "IP" ] && ! modprobe l2tp_ip; then |
190 |
# The config matches an existing tunnel. |
129 |
eend 1 "l2tp_ip module not present in your kernel (please enable CONFIG_L2TP_IP option in your kernel config)" |
191 |
true |
130 |
return 1 |
192 |
elif [ "${encap}" = ip ] && ! modprobe l2tp_ip; then |
131 |
fi |
193 |
eend 1 "Couldn't load the l2tp_ip module (perhaps the CONFIG_L2TP_IP kernel option is disabled)" |
132 |
# Search for an existing tunnel with the same ID |
|
|
133 |
local f_tunnel_id f_encap f_local f_remote f_peer_tunnel_id f_udp_sport f_udp_dport |
134 |
if _l2tp_get_tunnel_info f_ $t_tunnel_id; then |
135 |
# check if the existing tunnel has the same property than expected |
136 |
if [ "tunnel_id:$f_tunnel_id;encap:$f_encap;local:$f_local;remote:$f_remote; |
137 |
peer_tunnel_id:$f_peer_tunnel_id;udp_sport:$f_udp_sport;udp_dport:$f_udp_dport" \ |
138 |
!= "tunnel_id:$t_tunnel_id;encap:${t_encap^^};local:$t_local;remote:$t_remote; |
139 |
peer_tunnel_id:$t_peer_tunnel_id;udp_sport:$t_udp_sport;udp_dport:$t_udp_dport" ]; then |
140 |
eend 1 "There are an existing tunnel with id=$s_tunnel_id, but the properties mismatch with the one you want to create" |
141 |
return 1 |
142 |
fi |
143 |
else |
194 |
else |
144 |
veinfo ip l2tp add tunnel ${l2tptunnel} |
195 |
ebegin "Creating L2TPv3 tunnel (tunnel_id ${tunnel_id})" |
145 |
_ip_l2tp_add tunnel ${l2tptunnel} || return 1 |
196 |
printf %s "l2tp add tunnel ${l2tptunnel}" \ |
|
|
197 |
| xargs -E '' ip |
198 |
eend $? |
146 |
fi |
199 |
fi |
147 |
elif ! ip l2tp show tunnel | grep -Eq "^Tunnel $s_tunnel_id,"; then |
200 |
elif ! _l2tp_has_tunnel "${tunnel_id}"; then |
148 |
# no l2tptunnel_<INTF> declaration, assume that the tunnel is already present |
201 |
# A tunnel may incorporate more than one session (link). This |
149 |
# checking if tunnel_id exists otherwise raise an error |
202 |
# module allows for the user not to define a tunnel for a given |
150 |
eend 1 "Tunnel id=$s_tunnel_id no found (you may have to set l2tptunnel_${IFVAR})" |
203 |
# session. In that case, it will be expected that the required |
151 |
return 1 |
204 |
# tunnel has already been created to satisfy some other session. |
152 |
fi |
205 |
eend 1 "Tunnel #${tunnel_id} not found (defining ${key} may be required)" |
153 |
veinfo ip l2tp add session ${l2tpsession} name "${IFACE}" |
206 |
fi || return |
154 |
_ip_l2tp_add session ${l2tpsession} name "${IFACE}" || return 1 |
|
|
155 |
_up |
156 |
} |
157 |
|
207 |
|
|
|
208 |
ebegin "Creating L2TPv3 session (session_id ${session_id} tunnel_id ${tunnel_id})" |
209 |
printf %s "l2tp add session ${l2tpsession} name ${IFACE:?}" \ |
210 |
| xargs -E '' ip && _up |
211 |
eend $? |
212 |
} |
158 |
|
213 |
|
159 |
l2tp_post_stop() |
214 |
l2tp_post_stop() |
160 |
{ |
215 |
{ |
161 |
local session_id tunnel_id |
216 |
local existing_session session_id tunnel_id |
162 |
_is_l2tp || return 0 |
217 |
|
163 |
|
218 |
# This function may be invoked for every interface. If not a virtual |
164 |
ebegin "Destroying L2TPv3 link ${IFACE}" |
219 |
# interface, it can't possibly be one that's managed by this module, in |
165 |
veinfo ip l2tp del session tunnel_id $tunnel_id session_id $session_id |
220 |
# which case running ip(8) and awk(1) would be a needless expense. |
166 |
ip l2tp del session tunnel_id $tunnel_id session_id $session_id |
221 |
[ -e /sys/devices/virtual/net/"${IFACE:?}" ] \ |
167 |
if ! ip l2tp show session | grep -Eq "^Session [0-9]+ in tunnel $tunnel_id\$"; then |
222 |
&& existing_session=$(_l2tp_parse_existing_session 2>/dev/null) \ |
168 |
#tunnel $tunnel_id no longer used, destoying it... |
223 |
|| return 0 |
169 |
veinfo ip l2tp del tunnel tunnel_id $tunnel_id |
224 |
|
170 |
ip l2tp del tunnel tunnel_id $tunnel_id |
225 |
eval "${existing_session}" |
|
|
226 |
set -- session_id "${session_id}" tunnel_id "${tunnel_id}" |
227 |
ebegin "Destroying L2TPv3 session ($*)" |
228 |
ip l2tp del session "$@" |
229 |
eend $? && |
230 |
if ! _l2tp_in_session "${tunnel_id}"; then |
231 |
shift 2 |
232 |
ebegin "Destroying L2TPv3 tunnel ($*)" |
233 |
ip l2tp del tunnel "$@" |
234 |
eend $? |
171 |
fi |
235 |
fi |
172 |
eend $? |
|
|
173 |
} |
236 |
} |
174 |
- |
|
|