You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

415 lines
12 KiB

  1. #!/bin/sh
  2. # Copyright (C) 2016-2019 Aleksander Morgado <aleksander@aleksander.es>
  3. [ -x /usr/bin/mmcli ] || exit 0
  4. [ -x /usr/sbin/pppd ] || exit 0
  5. [ -n "$INCLUDE_ONLY" ] || {
  6. . /lib/functions.sh
  7. . ../netifd-proto.sh
  8. . ./ppp.sh
  9. init_proto "$@"
  10. }
  11. cdr2mask ()
  12. {
  13. # Number of args to shift, 255..255, first non-255 byte, zeroes
  14. set -- $(( 5 - ($1 / 8) )) 255 255 255 255 $(( (255 << (8 - ($1 % 8))) & 255 )) 0 0 0
  15. if [ "$1" -gt 1 ]
  16. then
  17. shift "$1"
  18. else
  19. shift
  20. fi
  21. echo "${1-0}"."${2-0}"."${3-0}"."${4-0}"
  22. }
  23. # This method expects as first argument a list of key-value pairs, as returned by mmcli --output-keyvalue
  24. # The second argument must be exactly the name of the field to read
  25. #
  26. # Sample output:
  27. # $ mmcli -m 0 -K
  28. # modem.dbus-path : /org/freedesktop/ModemManager1/Modem/0
  29. # modem.generic.device-identifier : ed6eff2e3e0f90463da1c2a755b2acacd1335752
  30. # modem.generic.manufacturer : Dell Inc.
  31. # modem.generic.model : DW5821e Snapdragon X20 LTE
  32. # modem.generic.revision : T77W968.F1.0.0.4.0.GC.009\n026
  33. # modem.generic.carrier-configuration : GCF
  34. # modem.generic.carrier-configuration-revision : 08E00009
  35. # modem.generic.hardware-revision : DW5821e Snapdragon X20 LTE
  36. # ....
  37. modemmanager_get_field() {
  38. local list=$1
  39. local field=$2
  40. local value=""
  41. [ -z "${list}" ] || [ -z "${field}" ] && return
  42. # there is always at least a whitespace after each key, and we use that as part of the
  43. # key matching we do (e.g. to avoid getting 'modem.generic.state-failed-reason' as a result
  44. # when grepping for 'modem.generic.state'.
  45. line=$(echo "${list}" | grep "${field} ")
  46. value=$(echo ${line#*:})
  47. # not found?
  48. [ -n "${value}" ] || return 2
  49. # only print value if set
  50. [ "${value}" != "--" ] && echo "${value}"
  51. return 0
  52. }
  53. # build a comma-separated list of values from the list
  54. modemmanager_get_multivalue_field() {
  55. local list=$1
  56. local field=$2
  57. local value=""
  58. local length idx item
  59. [ -z "${list}" ] || [ -z "${field}" ] && return
  60. length=$(modemmanager_get_field "${list}" "${field}.length")
  61. [ -n "${length}" ] || return 0
  62. [ "$length" -ge 1 ] || return 0
  63. idx=1
  64. while [ $idx -le "$length" ]; do
  65. item=$(modemmanager_get_field "${list}" "${field}.value\[$idx\]")
  66. [ -n "${item}" ] && [ "${item}" != "--" ] && {
  67. [ -n "${value}" ] && value="${value}, "
  68. value="${value}${item}"
  69. }
  70. idx=$((idx + 1))
  71. done
  72. # nothing built?
  73. [ -n "${value}" ] || return 2
  74. # only print value if set
  75. echo "${value}"
  76. return 0
  77. }
  78. modemmanager_cleanup_connection() {
  79. local modemstatus="$1"
  80. local bearercount idx bearerpath
  81. bearercount=$(modemmanager_get_field "${modemstatus}" "modem.generic.bearers.length")
  82. # do nothing if no bearers reported
  83. [ -n "${bearercount}" ] && [ "$bearercount" -ge 1 ] && {
  84. # explicitly disconnect just in case
  85. mmcli --modem="${device}" --simple-disconnect >/dev/null 2>&1
  86. # and remove all bearer objects, if any found
  87. idx=1
  88. while [ $idx -le "$bearercount" ]; do
  89. bearerpath=$(modemmanager_get_field "${modemstatus}" "modem.generic.bearers.value\[$idx\]")
  90. mmcli --modem "${device}" --delete-bearer="${bearerpath}" >/dev/null 2>&1
  91. idx=$((idx + 1))
  92. done
  93. }
  94. }
  95. modemmanager_connected_method_ppp() {
  96. local interface="$1"
  97. local ttyname="$2"
  98. local username="$3"
  99. local password="$4"
  100. proto_run_command "${interface}" /usr/sbin/pppd \
  101. "${ttyname}" \
  102. 115200 \
  103. nodetach \
  104. noaccomp \
  105. nobsdcomp \
  106. nopcomp \
  107. novj \
  108. noauth \
  109. ${username:+ user $username} \
  110. ${password:+ password $password} \
  111. lcp-echo-failure 5 \
  112. lcp-echo-interval 15 \
  113. lock \
  114. crtscts \
  115. nodefaultroute \
  116. usepeerdns \
  117. ipparam "${interface}" \
  118. ip-up-script /lib/netifd/ppp-up \
  119. ip-down-script /lib/netifd/ppp-down
  120. }
  121. modemmanager_disconnected_method_ppp() {
  122. local interface="$1"
  123. echo "running disconnection (ppp method)"
  124. [ -n "${ERROR}" ] && {
  125. local errorstring
  126. errorstring=$(ppp_exitcode_tostring "${ERROR}")
  127. case "$ERROR" in
  128. 0)
  129. ;;
  130. 2)
  131. proto_notify_error "$interface" "$errorstring"
  132. proto_block_restart "$interface"
  133. ;;
  134. *)
  135. proto_notify_error "$interface" "$errorstring"
  136. ;;
  137. esac
  138. } || echo "pppd result code not given"
  139. proto_kill_command "$interface"
  140. }
  141. modemmanager_connected_method_dhcp() {
  142. local interface="$1"
  143. local wwan="$2"
  144. local metric="$3"
  145. proto_init_update "${wwan}" 1
  146. proto_send_update "${interface}"
  147. json_init
  148. json_add_string name "${interface}_4"
  149. json_add_string ifname "@${interface}"
  150. json_add_string proto "dhcp"
  151. proto_add_dynamic_defaults
  152. [ -n "$metric" ] && json_add_int metric "${metric}"
  153. json_close_object
  154. ubus call network add_dynamic "$(json_dump)"
  155. }
  156. modemmanager_disconnected_method_dhcp() {
  157. local interface="$1"
  158. echo "running disconnection (dhcp method)"
  159. proto_init_update "*" 0
  160. proto_send_update "${interface}"
  161. }
  162. modemmanager_connected_method_static() {
  163. local interface="$1"
  164. local wwan="$2"
  165. local address="$3"
  166. local prefix="$4"
  167. local gateway="$5"
  168. local mtu="$6"
  169. local dns1="$7"
  170. local dns2="$8"
  171. local metric="$9"
  172. local mask=""
  173. [ -n "${address}" ] || {
  174. proto_notify_error "${interface}" ADDRESS_MISSING
  175. return
  176. }
  177. [ -n "${prefix}" ] || {
  178. proto_notify_error "${interface}" PREFIX_MISSING
  179. return
  180. }
  181. mask=$(cdr2mask "${prefix}")
  182. # TODO: mtu reporting in proto handler
  183. proto_init_update "${wwan}" 1
  184. echo "adding IPv4 address ${address}, netmask ${mask}"
  185. proto_add_ipv4_address "${address}" "${mask}"
  186. [ -n "${gateway}" ] && {
  187. echo "adding default IPv4 route via ${gateway}"
  188. proto_add_ipv4_route "0.0.0.0" "0" "${gateway}" "${address}"
  189. }
  190. [ -n "${dns1}" ] && {
  191. echo "adding primary DNS at ${dns1}"
  192. proto_add_dns_server "${dns1}"
  193. }
  194. [ -n "${dns2}" ] && {
  195. echo "adding secondary DNS at ${dns2}"
  196. proto_add_dns_server "${dns2}"
  197. }
  198. [ -n "$metric" ] && json_add_int metric "${metric}"
  199. proto_send_update "${interface}"
  200. }
  201. modemmanager_disconnected_method_static() {
  202. local interface="$1"
  203. echo "running disconnection (static method)"
  204. proto_init_update "*" 0
  205. proto_send_update "${interface}"
  206. }
  207. proto_modemmanager_init_config() {
  208. proto_config_add_string "device:device"
  209. proto_config_add_string apn
  210. proto_config_add_string username
  211. proto_config_add_string password
  212. proto_config_add_string pincode
  213. proto_config_add_string iptype
  214. proto_config_add_boolean lowpower
  215. }
  216. proto_modemmanager_setup() {
  217. local interface="$1"
  218. local modempath modemstatus bearercount bearerpath connectargs bearerstatus beareriface
  219. local operatorname operatorid registration accesstech signalquality
  220. local device apn username password pincode iptype metric
  221. local address prefix gateway mtu dns1 dns2
  222. json_get_vars device apn username password pincode iptype metric
  223. # validate sysfs path given in config
  224. [ -n "${device}" ] || {
  225. echo "No device specified"
  226. proto_notify_error "${interface}" NO_DEVICE
  227. proto_set_available "${interface}" 0
  228. return 1
  229. }
  230. [ -e "${device}" ] || {
  231. echo "Device not found in sysfs"
  232. proto_set_available "${interface}" 0
  233. return 1
  234. }
  235. # validate that ModemManager is handling the modem at the sysfs path
  236. modemstatus=$(mmcli --modem="${device}" --output-keyvalue)
  237. modempath=$(modemmanager_get_field "${modemstatus}" "modem.dbus-path")
  238. [ -n "${modempath}" ] || {
  239. echo "Device not managed by ModemManager"
  240. proto_notify_error "${interface}" DEVICE_NOT_MANAGED
  241. proto_set_available "${interface}" 0
  242. return 1
  243. }
  244. echo "modem available at ${modempath}"
  245. # always cleanup before attempting a new connection, just in case
  246. modemmanager_cleanup_connection "${modemstatus}"
  247. # setup connect args; APN mandatory (even if it may be empty)
  248. echo "starting connection with apn '${apn}'..."
  249. connectargs="apn=${apn}${username:+,user=${username}}${password:+,password=${password}}${pincode:+,pin=${pincode}}${iptype:+,ip-type=${iptype}}"
  250. mmcli --modem="${device}" --timeout 120 --simple-connect="${connectargs}" || {
  251. proto_notify_error "${interface}" CONNECT_FAILED
  252. proto_block_restart "${interface}"
  253. return 1
  254. }
  255. # log additional useful information
  256. modemstatus=$(mmcli --modem="${device}" --output-keyvalue)
  257. operatorname=$(modemmanager_get_field "${modemstatus}" "modem.3gpp.operator-name")
  258. [ -n "${operatorname}" ] && echo "network operator name: ${operatorname}"
  259. operatorid=$(modemmanager_get_field "${modemstatus}" "modem.3gpp.operator-code")
  260. [ -n "${operatorid}" ] && echo "network operator MCCMNC: ${operatorid}"
  261. registration=$(modemmanager_get_field "${modemstatus}" "modem.3gpp.registration-state")
  262. [ -n "${registration}" ] && echo "registration type: ${registration}"
  263. accesstech=$(modemmanager_get_multivalue_field "${modemstatus}" "modem.generic.access-technologies")
  264. [ -n "${accesstech}" ] && echo "access technology: ${accesstech}"
  265. signalquality=$(modemmanager_get_field "${modemstatus}" "modem.generic.signal-quality.value")
  266. [ -n "${signalquality}" ] && echo "signal quality: ${signalquality}%"
  267. # we won't like it if there are more than one bearers, as that would mean the
  268. # user manually created them, and that's unsupported by this proto
  269. bearercount=$(modemmanager_get_field "${modemstatus}" "modem.generic.bearers.length")
  270. [ -n "${bearercount}" ] && [ "$bearercount" -eq 1 ] || {
  271. proto_notify_error "${interface}" INVALID_BEARER_LIST
  272. return 1
  273. }
  274. # load connected bearer information
  275. bearerpath=$(modemmanager_get_field "${modemstatus}" "modem.generic.bearers.value\[1\]")
  276. bearerstatus=$(mmcli --bearer "${bearerpath}" --output-keyvalue)
  277. # load network interface and method information
  278. beareriface=$(modemmanager_get_field "${bearerstatus}" "bearer.status.interface")
  279. bearermethod=$(modemmanager_get_field "${bearerstatus}" "bearer.ipv4-config.method")
  280. echo "connection setup required in interface ${beareriface}: ${bearermethod}"
  281. case "${bearermethod}" in
  282. "dhcp")
  283. modemmanager_connected_method_dhcp "${interface}" "${beareriface}" "${metric}"
  284. ;;
  285. "static")
  286. address=$(modemmanager_get_field "${bearerstatus}" "bearer.ipv4-config.address")
  287. prefix=$(modemmanager_get_field "${bearerstatus}" "bearer.ipv4-config.prefix")
  288. gateway=$(modemmanager_get_field "${bearerstatus}" "bearer.ipv4-config.gateway")
  289. mtu=$(modemmanager_get_field "${bearerstatus}" "bearer.ipv4-config.mtu")
  290. dns1=$(modemmanager_get_field "${bearerstatus}" "bearer.ipv4-config.dns.value\[1\]")
  291. dns2=$(modemmanager_get_field "${bearerstatus}" "bearer.ipv4-config.dns.value\[2\]")
  292. modemmanager_connected_method_static "${interface}" "${beareriface}" "${address}" "${prefix}" "${gateway}" "${mtu}" "${dns1}" "${dns2}" "${metric}"
  293. ;;
  294. "ppp")
  295. modemmanager_connected_method_ppp "${interface}" "${beareriface}" "${username}" "${password}"
  296. ;;
  297. *)
  298. proto_notify_error "${interface}" UNKNOWN_METHOD
  299. return 1
  300. ;;
  301. esac
  302. return 0
  303. }
  304. proto_modemmanager_teardown() {
  305. local interface="$1"
  306. local modemstatus bearerpath errorstring
  307. local device lowpower
  308. json_get_vars device lowpower
  309. echo "stopping network"
  310. # load connected bearer information, just the first one should be ok
  311. modemstatus=$(mmcli --modem="${device}" --output-keyvalue)
  312. bearerpath=$(modemmanager_get_field "${modemstatus}" "modem.generic.bearers.value\[1\]")
  313. [ -n "${bearerpath}" ] || {
  314. echo "couldn't load bearer path"
  315. return
  316. }
  317. # load bearer connection method
  318. bearerstatus=$(mmcli --bearer "${bearerpath}" --output-keyvalue)
  319. bearermethod=$(modemmanager_get_field "${bearerstatus}" "bearer.ipv4-config.method")
  320. [ -n "${bearermethod}" ] || {
  321. echo "couldn't load bearer method"
  322. return
  323. }
  324. case "${bearermethod}" in
  325. "dhcp")
  326. modemmanager_disconnected_method_dhcp "${interface}"
  327. ;;
  328. "static")
  329. modemmanager_disconnected_method_static "${interface}"
  330. ;;
  331. "ppp")
  332. modemmanager_disconnected_method_ppp "${interface}"
  333. ;;
  334. *)
  335. ;;
  336. esac
  337. # disconnect
  338. mmcli --modem="${device}" --simple-disconnect ||
  339. proto_notify_error "${interface}" DISCONNECT_FAILED
  340. # disable
  341. mmcli --modem="${device}" --disable
  342. # low power, only if requested
  343. [ "${lowpower:-0}" -lt 1 ] ||
  344. mmcli --modem="${device}" --set-power-state-low
  345. }
  346. [ -n "$INCLUDE_ONLY" ] || {
  347. add_protocol modemmanager
  348. }