From b7a0dca0ecab2f46cf6b568c8980fcaf4d0770bc Mon Sep 17 00:00:00 2001 From: Jaromil Date: Wed, 8 Jun 2016 12:09:24 +0200 Subject: [PATCH] refurbish network and rest api from dowse development --- zuper | 293 ++++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 214 insertions(+), 79 deletions(-) diff --git a/zuper b/zuper index 4709f48..c8224d6 100755 --- a/zuper +++ b/zuper @@ -33,6 +33,9 @@ arrs=(req freq) vars+=(zuper_version) zuper_version=0.2 +zmodload zsh/system +zmodload zsh/net/tcp + # {{{ Messaging # Messaging function with pretty coloring @@ -209,7 +212,6 @@ catch() { function TRAPZERR() { } } # Endgame handling arrs+=(destruens) -destruens=() # Trap functions for the endgame event TRAPINT() { endgame INT; return $? } @@ -292,21 +294,133 @@ strtok() { for c in {1..${#_string}}; do if [[ "${_string[(e)$c]}" == "$_delim" ]]; then # check if not empty - t=${_string[(e)$(($f + 1)),$(($c - 1))]} - [[ "$t" == "" ]] || tok+=($t) + t="${_string[(e)$(($f + 1)),$(($c - 1))]}" + if [[ "$t" == "" ]]; then + tok+=("null") + else + tok+=("$t") + fi # save last found f=$c fi done # add last token t=${_string[(e)$(($f + 1)),$c]} - [[ "$t" == "" ]] || tok+=($t) + if [[ "$t" == "" ]]; then + tok+=("null") + else + tok+=("$t") + fi } # TODO: move in here some helpers # }}} Strings +# {{{ Networking + +# This is only tested on GNU/Linux and makes use of sysfs + +# index of all network devices +arrs+=(net_devices) + +# map of ipv4 assigned addresses: [dev addr] +maps+=(net_ip4_addr) +# map of ipv6 assigned addresses: [dev addr] +maps+=(net_ip6_addr) + +# map of dhcp served ipv4 +maps+=(ip4dhcps) +# map of dhcp served ipv6 +maps+=(ip6dhcps) + +# map of external ipv4 addresses +maps+=(net_ip4_exit) +# map of internal ipv6 addresses +# maps+=(ip6exits) + +net.scan_devices() { + for i in "${(f)$(find /sys/devices/ -name net)}"; do + dev=`ls --indicator-style=none $i` + + # skip the loopback device + [[ "$dev" =~ "^lo" ]] && continue + func "$dev" + net_devices+=($dev) + done + + # return error if no device found + if [[ ${#net_devices} = 0 ]]; then return 1 + else return 0; fi +} + +net.scan_addresses() { + [[ ${#net_devices} = 0 ]] && { + error "No network device found." + func "Have you ran net.scan_devices() first?" + return 1 + } + + for dev in ${net_devices}; do + # check ipv4 connections + conn=`ip addr show $dev | awk '/inet / {print $2}'` + [[ "$conn" = "" ]] || { + net_ip4_addr+=($dev $conn) } + # check ipv6 connections + conn=`ip addr show $dev | awk '/inet6/ {print $2}'` + [[ "$conn" = "" ]] || { + net_ip6_addr+=($dev $conn) } + done + + # list ipv4 + notice "${#net_ip4_addr} ipv4 connected devices found" + for c in ${(k)net_ip4_addr}; do + act " $c ${net_ip4_addr[$c]}" + done + + # list ipv6 + notice "${#net_ip6_addr} ipv6 connected devices found" + for c in ${(k)net_ip6_addr}; do + act " $c ${net_ip6_addr[$c]}" + done + # find out network addresses + + return 0 +} + +net.scan_exits() { + # just ipv4 for now, also we use curl to drive the call over the + # specific interface, but if that wouldn't matter then rest.get is + # better to avoid this dependency + + for dev in ${(k)net_ip4_addr}; do + addr=`curl --silent --interface $dev https://api.ipify.org` + if [[ "$?" != "0" ]]; then + error "curl returns $?: $addr" + else + [[ "$addr" = "" ]] || { + notice "$dev external ip: $addr" + net_ip4_exit+=($dev $addr) + } + fi + done + + for dev in ${(k)net_ip6_addr}; do + addr=`curl --silent --ipv6 --interface $dev https://api.ipify.org` + if [[ $? != 0 ]]; then + error "curl returns $?: $addr" + else + [[ "$addr" = "" ]] || { + notice "$dev external ip: $addr" + net_ip4_exit+=($dev $addr) + } + fi + done + +} + +# }}} Networking + # {{{ Key/Value filesave # optional: define zkv=1 on source @@ -316,7 +430,6 @@ strtok() { ########################## # Key/Value file storage using ZSh associative maps - zmodload zsh/system # load a map from a file # map must be already instantiated with typeset -A by called @@ -382,44 +495,40 @@ EOF # {{{ Get/Set REST API -# optional: define restful=1 on source +######## +# Restful API client +# there is a clear zsh optimization here in get/set kv +# using zsh/tcp instead of spawning curl +# and perhaps querying with one call using ?recursive -[[ "$restful" = "" ]] || { +vars+=(rest_reply_body rest_reply_header) +maps+=(rest_header) - ######## - # Restful API client - # there is a clear zsh optimization here in get/set kv - # using zsh/tcp instead of spawning curl - # and perhaps querying with one call using ?recursive +function rest.put() { + fn "rest.put $*" - zmodload zsh/net/tcp + # $1 = hostname + # $2 = port + # $3 = path + # value from stdin | + # to check if the http service is running is up to the caller - function restful.put() { - fn "restful.put $*" + _host=${1} # ip address + _port=${2} + _path=${3} + sysread _v - # $1 = hostname - # $2 = port - # $3 = path - # value from stdin | + req=(_host) + ckreq || return $? - # to check if the http service is running is up to the caller + if ztcp $_host $_port; then - _host=${1} # ip address - _port=${2} - _path=${3} - sysread _v + # TODO: work out various parsers, this one works with consul.io - req=(_host) - ckreq || return $? - - if ztcp $_host $_port; then - - # TODO: work out various parsers, this one works with consul.io - - _fd=$REPLY - # func "tcp open on fd $fd" - cat <& $_fd + _fd=$REPLY + # func "tcp open on fd $fd" + cat <& $_fd PUT ${_path} HTTP/1.1 User-Agent: Zuper/$zuper_version Host: ${_host}:${_port} @@ -429,72 +538,98 @@ Content-Type: application/x-www-form-urlencoded EOF - print -n "$_v" >& $_fd + print -n "$_v" >& $_fd - sysread -i $_fd _res + sysread -i $_fd _res - # close connection - ztcp -c $_fd + # close connection + ztcp -c $_fd - [[ "$_res" =~ "true" ]] || { - warn "failed PUT on restful key/value" - warn "host: ${_host}" - warn "port: ${_port}" - warn "path: ${_path}" - warn "value: $_v" - print - "$_res" - zerr - return 1 - } - - else - error "cannot connect to restful service: $_host:$_port" - zerr - return 1 - fi - - return 0 - - } - - function restful.get() { - fn "restful.get $*" - - _host=${1} - _port=${2} - _path=${3} - - req=(_host _port) - ckreq || return $? - - ztcp $_host $_port || { + [[ "$_res" =~ "true" ]] || { + warn "failed PUT on restful key/value" + warn "host: ${_host}" + warn "port: ${_port}" + warn "path: ${_path}" + warn "value: $_v" + print - "$_res" zerr return 1 } - _fd=$REPLY + else + error "cannot connect to restful service: $_host:$_port" + zerr + return 1 + fi - # TODO: work out various parsers, this one works with consul.io + return 0 - cat <& $_fd +} + +function rest.get() { + fn "rest.get $*" + + _host=${1} + _port=${2} + _path=${3} + + req=(_host _port) + ckreq || return $? + + ztcp $_host $_port || { + zerr + return 1 + } + + _fd=$REPLY + + # TODO: work out various parsers, this one works with consul.io + + cat <& $_fd GET ${_path} HTTP/1.1 User-Agent: Zuper/$zuper_version Host: $_host:$_port Accept: */* EOF - sysread -i $_fd -o 1 | awk -F: ' -/"Value":/ { gsub(/"|}]/,"",$7) ; print $7 }' | base64 -d - # close connection - ztcp -c $_fd + # read header response + rest_reply=`sysread -i $_fd -o 1` - return 0 + for i in "${(f)rest_reply}"; do + print $i | hexdump -C + # first line is the response code - } + [[ "$i" -regex-match "\x0d\x0a$" ]] && { + func BLANK + break } + + # # save other lines in map for fast retrieval + # _field=${i[(ws@:@)1]} + # func "$_field - header field parsed" + # rest_header[$_field]="${i[(ws@:@)2]}" + + # c=$(( $c + 1 )) + done + # rest_reply_header="${(f)$(cat <&$_fd)}" + + func "${#rest_reply_header} bytes response header stored in rest_reply_header" + # | awk -F: ' + #/"Value":/ { gsub(/"|}]/,"",$7) ; print $7 }' | base64 -d + + # TODO: read content-length and use it here + + rest_reply_body="${(f)$(sysread -i $_fd -o 1)}" + func "${#rest_reply_body} bytes response body stored in rest_reply_body" + + # close connection + ztcp -c $_fd + + return 0 } + # }}} Get/Set REST API # {{{ Helpers