[HowTo] Discover a rogue DHCP-server on the network and report it to Zabbix

frater

Mitglied
Mitglied seit
23 Nov 2008
Beiträge
455
Punkte für Reaktionen
3
Punkte
18
I'm managing about 90 Fritzboxes that are running Freetz.
The main reason for Freetzing these boxes is the possibility to run the Zabbix agent on them.

When these routers are running this zabbix agent I can execute diagnostic commands on the Fritzbox that are initiated from the server.

I have written scripts in bash that will calculate the saturation of the DSL-connection and many other diagnostics that will inform me of a problem quite often sooner than that I get a call. Sometimes I have the problem fixed before they have called or while they are make the call. Anyhow, I can't imagine to be without it.

The system is constantly evolving as there is always stuff happening that could have been detected sooner.
One of these problems are rogue DHCP-servers. The Zabbix-server is monitoring much more than only these Fritzboxes. I also monitor Windows & Linux servers and switches if possible.
Our typical client is a small business without any IT personell and they are totally reliant on our services and help.
Quite often it happens that a 2nd DHCP-server appears in the network. This isn't always someone doing the wrong thing. This week there was a major power outage in Amsterdam and I later discovered that some routers of a popular cable provider suddenly had their DHCP-server turned on even though it was turned off several months ago. These DHCP-servers are turned off because these DHCP-server don't keep the static leases. Anyway....

I would really like to prevent this by adding this to monitoring. I thought of an UDP-scan which, I believe, is possible with tcpdump, but this would only work if the server is in the same network. I would also like to catch those Apple airports that have their DHCP-server turned on if they are connected wrong.

I found a piece of software which will probably give me all the means to pull this off, but I don't know how to convert this to a package. I would really appreciate it if someone could help me with this.

https://github.com/saravana815/dhtest/

edit: maybe this package can't be used (maybe it can). I'm open for suggestions. I noticed a reference to libnet (https://github.com/sam-github/libnet), but I can't compile it yet on an Ubuntu box, let alone for Freetz.
I don't even know yet how to use it in my scenario.

I can repay the favour to the community by describing exactly how I will use it as it needs a wrapper to give me the simple answer to the question if there is a 2nd DHCP-server.
 
Zuletzt bearbeitet:
Hi frater,

try to use tcpdump to analyze the dhcp requests and check for request answered by the wrong host. There is also a tool called dhcpdump out there but it is not yet available for freetz afaik.
 
Another (IMHO easier) way to detect an unwanted DHCP server is the "udhcpc" applet of a (complete) busybox. It may call a user-defined script, if a lease was obtained - how you have to call it to avoid interferences with FRITZ!OS interface settings, differs from model to model.

A 7390 with only a single local port (eth0) bound to the "lan" bridge may require a different handling than a 7490, where you can simply use "eth0" as "native port" to handle DHCP traffic. I've tested this once again only minutes ago against a (regular) dhcpd daemon (ISC) and got the following result, while the box was working with it's "native IP address":
Code:
Jan 20 17:59:39 central dhcpd: DHCPDISCOVER from 08:96:d7:6e:ca:fe via vlan7
Jan 20 17:59:40 central dhcpd: DHCPOFFER on 192.168.123.92 to 08:96:d7:6e:ca:fe via vlan7
Jan 20 17:59:41 central dhcpd: Wrote 0 deleted host decls to leases file.
Jan 20 17:59:41 central dhcpd: Wrote 0 new dynamic host decls to leases file.
Jan 20 17:59:41 central dhcpd: Wrote 67 leases to leases file.
Jan 20 17:59:41 central dhcpd: DHCPREQUEST for 192.168.123.92 (192.168.123.X) from 08:96:d7:6e:ca:fe via vlan7
Jan 20 17:59:41 central dhcpd: DHCPACK on 192.168.123.92 to 08:96:d7:6e:ca:fe via vlan7

But because the applet is only responsible for DHCP requests and releases, it's in most cases already harmless - as long as you don't assign the acquired IP address to an interface, it's "only gambling" and your only problem could be an "exhausted address space" syndrom on the irregular DHCP server, if you miss the release of any (really) acquired address(es).

If you run such a script on a regular base, you could detect the rascal ... the obvious problem, that your own (regular) DHCP server could offer an address too, can't be solved this way. Here you would need a more sophisticated client, which may be configured to ignore replies from special addresses (of the trusted DHCP server). The "udhcpc" applet may replace the "dhtest" program ... but it's not usable to "filter" the responses (but "dhtest" can't do this even, if I didn't miss anything while reading its description).

Doing it with "socat" is rather the "hard way" (but "socat" is available in Freetz) ... and here you could decide to ignore replies (offers) from a particular server and as long as you only want to detect the source IP of such an unauthorized answer, it's not so difficult to handle it with shell code. The ugliest task is the generation of a proper (binary) request, but for a DHCP request you could use a "one-time shot" of a packet with a fantasy MAC and no special options ... and store it in a file for reuse in each request.

As long as your only interest is the detection of a possible problem based on a DHCP reply from an unexpected source, the "socat" solution should be good enough (and simple to implement) ... but it's not the best solution, if you want to interpret the content of such a reply instead of its simple presence.
 
Zuletzt bearbeitet:
Hi Peter,

I will examine what I can do with "socat".
I am planning to run this every 15 minutes on every router that I have. Most of the time there is no rogue DHCP-server in that net.
This means the procedure I will be doing shouldn't have any effect on the legitimate DHCP-server.

It was quite easy to compile this program on an Ubuntu system with "gcc -c -o dhtest.o dhtest.c"
What would be the command to use to create a MIPS binary
I never needed this before and have no clue.
All the recipes expect a "configure" and even then it needs a lot of reading and practicing.
I would still like to be able to see what this "dhtest" does on a Freetz-box and I would also like to know how to compile a program for Freetz.
"

- - - Aktualisiert - - -

Hi Peter,

I did some more research and noticed some ground work has already been done.
nmap seemed to be the easiest way to detect a DHCP-server using a special script

I added nmap with 'make menuconfig' and made some changes to my zabbix config so the script gets included.
In the end it works on a normal system (not tested with rogue dhcp-servers yet), but not on freetz
It seems the scripting is not working for the Freetz implemention of nmap. 'nmap --? | grep script' does say it's possible, but it is not working.

Would it be possible to (optionally) include this support?
The scripts in /usr/share/nmap could be optional as well to save space.
It seems it just ignores the --script parameter


I was expecting this:

Code:
sudo nmap --script broadcast-dhcp-discover


Starting Nmap 6.40 ( http://nmap.org ) at 2017-01-22 10:09 CET
Pre-scan script results:
| broadcast-dhcp-discover:
|   IP Offered: 10.0.0.33
|   DHCP Message Type: DHCPOFFER
|   Subnet Mask: 255.255.255.0
|   Renewal Time Value: 4 days, 0:00:00
|   Rebinding Time Value: 7 days, 0:00:00
|   IP Address Lease Time: 8 days, 0:00:00
|   Server Identifier: 10.0.0.9
|   Router: 10.0.0.138
|   Domain Name Server: 10.0.0.9
|_  Domain Name: mrwolf.local
WARNING: No targets were specified, so 0 hosts scanned.
Nmap done: 0 IP addresses (0 hosts up) scanned in 0.25 seconds


Maybe I should revisit socat as you recommended.
The script for nmap may be of help in creating that "DHCPDISCOVER" package.
It does seem intimidating, but on the other hand it would make it all a bit cleaner.
The nmap scripts stops after a DHCP-server responds and there's a big chance I don't catch the rogue one.
I hoped to tackle that with multiple commands hoping the server that just replied doesn't want to respond now.

I would only need to create that DISCOVER package with socat and then listen for a few seconds what comes in using netcat.

If you can be of help, please do. But I will try to do it and report if I can get further...
Now for some Sunday things with the GF





Here's how far I got:

Code:
#!/bin/sh


# find rogue DHCP-server with nmap
# After adding nmap with make menuconfig one needs to add the ""broadcast-dhcp-discover" script 
# this script
#
# mkdir -p ~/trunk/make/nmap/files/root/usr/share/nmap/scripts
# wget -O ~/trunk/make/nmap/files/root/usr/share/nmap/scripts/broadcast-dhcp-discover.nse https://svn.nmap.org/nmap/scripts/broadcast-dhcp-discover.nse
#
# add this file
# mkdir -p ~/trunk/make/nmap/files/root/sbin
# wget -O ~/trunk/make/nmap/files/root/sbin/roguedhcp http://hmvc.eu/roguedhcp
#
IFACE=lan
VALID_DHCP=`mktemp`
FOUND_DHCP=`mktemp`
ROGUE_DHCP=`mktemp`
HEADLESS=
tty >/dev/null || HEADLESS=true


SUDO=/usr/bin/sudo
[ "${USER}" == 'root' ] && SUDO=''


echo "$*" | egrep -o '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' >${VALID_DHCP}




if netstat -anu | grep -q '0\.0\.0\.0:67' ; then # check if DHCP is running
  # add LAN IP on
  ifconfig ${IFACE} 2>/dev/null | grep -o 'inet addr:[0-9.]*' | awk -F: '{print $2}' >>${VALID_DHCP}
fi


# The typical Windows SBS has its IP on 192.168.16.2
# It makes things much easier for me to hardcode these instead of passing it
echo '192.168.16.2
192.168.16.4' >>${VALID_DHCP}


[ ${HEADLESS} ] || echo "Running $0 with these IP's `cat ${VALID_DHCP} | tr '\r\n' ' '`" >&2


# convert file to be used as a grep filter
sed -i 's/\./\\./g;s/.*/^&\$/g' ${VALID_DHCP}


# Execute it more times quickly in the hope that I can also catch the rogue DHCP-server
$SUDO nmap -e ${IFACE} -sU -p 67 --script broadcast-dhcp-discover 2>/dev/null | grep -o 'Server Identifier:.*' | grep -o '[0-9.]*' >>${FOUND_DHCP}
$SUDO nmap -e ${IFACE} -sU -p 67 --script broadcast-dhcp-discover 2>/dev/null | grep -o 'Server Identifier:.*' | grep -o '[0-9.]*' >>${FOUND_DHCP}
$SUDO nmap -e ${IFACE} -sU -p 67 --script broadcast-dhcp-discover 2>/dev/null | grep -o 'Server Identifier:.*' | grep -o '[0-9.]*' >>${FOUND_DHCP}


[ ${HEADLESS} ] || echo "Found these DHCP-servers: `sort ${FOUND_DHCP} | uniq | tr '\r\n' ' '`" >&2


sort ${FOUND_DHCP} | uniq | grep -vf ${VALID_DHCP} >${ROGUE_DHCP}


if [ ! -s ${ROGUE_DHCP} ] ; then
  # Zabbix needs a parameter or it will become unsupported. A dot is sent
  echo '.'
  [ ${HEADLESS} ] || echo "No rogue DHCP-servers found" >&2
else
  # when passed as a string to Zabbix it can be used for compares. A list can't
  cat ${ROGUE_DHCP} | tr '\r\n' ' '
  [ ${HEADLESS} ] || echo "Found these rogue DHCP-servers: `cat ${ROGUE_DHCP} | tr '\r\n' ' '`" >&2
fi


rm -f ${ROGUE_DHCP}
rm -f ${VALID_DHCP}
rm -f ${FOUND_DHCP}



I have had yet another look at nmap.
I ran it on an Ubuntu box (where it is working) with -v -d9 and saw it needed functions that it gets from a library. I hoped I could use the files of the nmap of the Ubuntu install (which is even a 6.4 version). It probably needs to be compiled different as well.
I think I need to give up on nmap and use the more basic socat.
But that means I need to study the DHCP-discover package and send that.

Added these files (but that didn't work)

Code:
/usr/share/nmap/
/usr/share/nmap/nmap-services
/usr/share/nmap/nmap.xsl
/usr/share/nmap/nse_main.lua
/usr/share/nmap/nselib
/usr/share/nmap/nselib/dhcp.lua
/usr/share/nmap/nselib/ipOps.lua
/usr/share/nmap/nselib/packet.lua
/usr/share/nmap/nselib/stdnse.lua
/usr/share/nmap/nselib/strict.lua
/usr/share/nmap/scripts
/usr/share/nmap/scripts/broadcast-dhcp-discover.nse
/usr/share/nmap/scripts/script.db
 
Zuletzt bearbeitet:
The DHCP protocol (RFC2131) covers the whole process (look at p. 13-15) ... if a client sends a DHCPDISCOVER as a broadcast (thats why you need "socat" instead of "netcat" for the sender), each DHCP server may send a DHCPOFFER reply. It's only a decision of the client, which of these offers will be accepted and after the selection was made, a first DHCPREQUEST is (usually) send as a broadcast - to notify all servers, which one of them was selected by the client - and further "conversation" goes on with unicast packets.

As a result, you haven't to worry about multiple calls ... it should be sufficient to evaluate each received DHCPOFFER packet and check its source, if the responding server is authorized or not. The final action should (or better "could") be sending a DHCPREQUEST broadcast with a "server identifier" regarding none of the servers (as long as you don't need an address) and all of them are "reset" to listening for further requests - because they must assume: "a neighbor was hired for this job". But this final DHCPREQUEST isn't really necessary - have a look at p. 30 of the RFC text.

These two packets to broadcast (or only one, if you rely on the sentence: "Therefore, the servers may not receive a specific DHCPREQUEST from which they can decide whether or not the client has accepted the offer.") may be prepared only once (if I didn't miss any reason from RFC for individual packets) - the "hardware address" of the sender should never change and all other fields are zero or contain immutable values (p. 35). Because your client device already has a configured IP stack (aka has obtained/set up a valid IP address), there should be no problem to use "socat" and shell code.

You may find an example (covering packet creation, sending broadcasts and receiving answers) in "eva_discover" from my YourFritz repository (eva_tools sub-directory) and with some fantasy you could even adapt the "reading part" to evaluate the received answers and discover unauthorized DHCP offers.

The used "shell function library" has the advantage, that it's runnable using BusyBox on a FRITZ!Box device without any modification - you need only the already mentioned "socat" utility (Freetz contains the required package) and a (better rich- or full-featured, but AVM's original should work too) BusyBox binary to implement your solution.

If you discover problems or further questions, don't hesitate to request help ... but I would think, it's a fairly simple task to implement it - the example in "eva_discover" should show/clarify many aspects of (only one possible solution to do it) handling binary network data from shell code.
 
I will look into that today. I also thought of creating a secondary interface on "lan" and execute a DHCP-request on it whilst listening on that interface with tcpdump. In the same script I can destroy that interface.
But like I said. I will first examine the eva_tools folder.

BTW. The reason why I used the nmap more than once was because that script also handled the request and it stops if it gets a reply.
I knew I normally would have to initiate only 1 DHCPDISCOVER on 255.255.255.255 and then listen for the replies to come in.

- - - Aktualisiert - - -

If you discover problems or further questions, don't hesitate to request help ... but I would think, it's a fairly simple task to implement it - the example in "eva_discover" should show/clarify many aspects of (only one possible solution to do it) handling binary network data from shell code.

Hi Peter,

I would really appreciate you could fill in some blanks.
This is the first time I'm handling packets.
I have converted the necessary parts of the eva_tools package. By doing so I'm forcing myself to understand what each thing does.
Some bash things were new to me, like ${response:22:2} and ${#response}

I am right in assuming that EVA is working on port 5035 instead of 67??

I've tried to understand how yf_pack works, but I'm fairly uncertain. Wouldn't know how to elegantly cram in a MAC-address.
I have the MAC-address already in variable without the colons.
Didn't even get into response packet analysis.

Are you willing to go that extra mile for me?
I'll promise to really try and understand how it works...


Code:
freetz@freetz:~/trunk$ cat make/zabbix_agentd-support/files/root/sbin/roguedhcp
#!/bin/sh


# find rogue DHCP-server with nmap
# After adding nmap with make menuconfig one needs to add the ""broadcast-dhcp-discover" script and
# this script
#
# mkdir -p ~/trunk/make/nmap/files/root/usr/share/nmap/scripts
# wget -O ~/trunk/make/nmap/files/root/usr/share/nmap/scripts/broadcast-dhcp-discover.nse https://svn.nmap.org/nmap/scripts/broadcast-dhcp-discover.nse
#
# add this file
# mkdir -p ~/trunk/make/nmap/files/root/sbin
# wget -O ~/trunk/make/nmap/files/root/sbin/roguedhcp http://hmvc.eu/roguedhcp
#
# https://en.wikipedia.org/wiki/Dynamic_Host_Configuration_Protocol


for function in /functions/yf_* ; do
  echo "${function}" | grep -q yf_fritzos_model_settings.function && continue
  . $function
done
yf_is_fritzos_device && . /functions/yf_fritzos_model_settings.function


IFACE=lan
PROG="$0"
[ "${PROG}" = "-sh" ] && PROG="prompt"


TMPDIR=`mktemp -p /var/tmp -d ${PROG//*\/}.XXXXXXXXXX`
VALID_DHCP=${TMPDIR}/valid_dhcp
FOUND_DHCP=${TMPDIR}/found_dhcp
ROGUE_DHCP=${TMPDIR}/rogue_dhcp


HEADLESS=
tty >/dev/null || HEADLESS=true


SUDO=`which sudo`
[ "${USER}" == 'root' ] && SUDO=''
SOCAT="${SUDO} socat"


FREETZ_IP=`ifconfig ${IFACE} 2>/dev/null | grep -o 'inet addr:[0-9.]*' | awk -F: '{print $2}'`
SECOND_IP=`ifconfig ${IFACE}:0 2>/dev/null | grep -o 'inet addr:[0-9.]*' | awk -F: '{print $2}'`
FREETZ_MAC="`ifconfig lan | grep -o 'HWaddr.*' | awk '{print $2}' | tr -d ':'`"


DEFAULT_WAIT=1


BOOTP_SERVER_PORT=67
BOOTP_CLIENT_PORT=68


discoverdhcp()
{


  # prepare discovery packet for yf_pack
  Op=1
  HType=1
  HLen=6
  Hops=0
  Secs=0
  Flags=32768           # 1000 0000


  head -c4 /dev/urandom >${TMPDIR}/xid


  # The packet buffer file
  DISCOVER_PACKETFILE="${TMPDIR}/discover.packet"


  # Overwrite first bytes with output of yf_pack
  yf_pack 8 $Op 8 $HType 8 $HLen 8 $Hops >${DISCOVER_PACKETFILE}  # discovery header
  cat ${TMPDIR}/xid                      >>${DISCOVER_PACKETFILE} # XID 4 random octets
  yf_pack 16 $Secs 16 $Flags             >>${DISCOVER_PACKETFILE} # secs and flags
  head -c16                  < /dev/zero >>${DISCOVER_PACKETFILE} # 4 x 4 zeroes
  yf_hex2bin ${FREETZ_MAC}               >>${DISCOVER_PACKETFILE} # mac-address
  head -c202                 < /dev/zero >>${DISCOVER_PACKETFILE} # 192 zeroes
  yf_pack 8 99 8 130 8 83 8 99 8 1       >>${DISCOVER_PACKETFILE} # magic cookie and discovery mark
  head -c71                  < /dev/zero >>${DISCOVER_PACKETFILE} # 81 zeroes


  if ! [ ${HEADLESS} ] ; then
    echo -e "I constructed a packet of `stat -c%s ${DISCOVER_PACKETFILE}` bytes containing:\n\n" >&2
    od -x ${DISCOVER_PACKETFILE} >&2
  fi


  MAIN=$$


  $SOCAT UDP4-LISTEN:${BOOTP_CLIENT_PORT},bind=${SECOND_IP} FILE:${TMPDIR}/response,creat &
  LISTENER=$!


  $SOCAT PIPE:${TMPDIR}/broadcast,ignoreeof UDP4-DATAGRAM:255.255.255.255:${BOOTP_SERVER_PORT},broadcast,bind=${SECOND_IP} &
  BROADCASTER=$!


  cat ${DISCOVER_PACKETFILE} >${TMPDIR}/broadcast
  [ -d /proc/${BROADCASTER} ] && kill ${BROADCASTER} 2>/dev/null 1>&2
  sleep 1


  [ -d /proc/${LISTENER} ]    && kill ${LISTENER} 2>/dev/null 1>&2
  wait ${LISTENER}


  if ! [ ${HEADLESS} ] ; then
    echo -e "I found an answer of `stat -c%s ${TMPDIR}/response` bytes containing:\n\n" >&2
    od -x ${TMPDIR}/response >&2
  fi


  RESPONSE=$(cat ${TMPDIR}/response 2>/dev/null | yf_bin2hex)
  if [ ${#RESPONSE} -gt 0 -a x${RESPONSE:8:8} == x02000000 ]; then
    # we assume, that the LE value of 2 above represents an answer and
    # if it's found, we assume the next 4 bytes are the IPv4 address
    # in LE format
    IP=$(yf_print_ip ${RESPONSE:22:2}${RESPONSE:20:2}${RESPONSE:18:2}${RESPONSE:16:2})
    [ ${HEADLESS} ] || echo "$IP"
  fi
}




echo "$*" | egrep -o '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' >${VALID_DHCP}


if netstat -anu | grep -q '0\.0\.0\.0:67' ; then # check if DHCP is running
  # add Freetz's IP to ignore
  echo ${FREETZ_IP} >>${VALID_DHCP}
fi


# The typical Windows SBS has its IP on 192.168.16.2
# It makes things much easier for me to hardcode these instead of passing it
echo '192.168.16.2
192.168.16.4' >>${VALID_DHCP}


[ ${HEADLESS} ] || echo "Running $0 ignoring these IP's I assume are valid `cat ${VALID_DHCP} | tr '\r\n' ' '`" >&2


# convert file to be used as a grep filter
sed -i 's/\./\\./g;s/.*/^&\$/g' ${VALID_DHCP}


# Execute it more times quickly in the hope that I can also catch the rogue DHCP-server
discoverdhcp  >>${FOUND_DHCP}


[ ${HEADLESS} ] || echo "Found these DHCP-servers: `sort ${FOUND_DHCP} | uniq | tr '\r\n' ' '`" >&2


sort ${FOUND_DHCP} | uniq | grep -vf ${VALID_DHCP} >${ROGUE_DHCP}


if [ ! -s ${ROGUE_DHCP} ] ; then
  # Zabbix needs a parameter or it will become unsupported. A dot is sent
  echo '.'
  [ ${HEADLESS} ] || echo "No rogue DHCP-servers found" >&2
else
  # when passed as a string to Zabbix it can be used for compares. A list can't
  cat ${ROGUE_DHCP} | tr '\r\n' ' '
  [ ${HEADLESS} ] || echo "Found these rogue DHCP-servers: `cat ${ROGUE_DHCP} | tr '\r\n' ' '`" >&2
fi


rm -rf ${TMPDIR}

Am I correct in that I can't use port 67 to listen on if the DHCP-server is running?
I believe I should be able to use port 68 for that?

I don't know if yf_pack is properly running.
Not really getting any output there.
I've sourced "yourfritz_helpers"

25-01-2017

The difficulties I'm having:

  • I don't know how to use yf_pack for bytes in raw format (like the XID I created with /dev/urandom) edit: I now used "od -l -An"... am I using it correctly here?
  • I don't know how to insert a MAC (maybe yf_pack could need and addition?)
  • I'm not sure I need to come from port 68 for a client request, if so, I will have a problem as that port is taken by the listening process (should I use lowport instead?)
  • Is it better to use the lan:0 interface on which the 169.254.1.1 address is always active?
  • I believe the first answer is multicast. correct?
  • many other details....


30-01-2017
@PeterPawn do you have some time to revisit this?
I've updated the code to what I have so far.
I'm confused with "od" not dumping the whole file I'm preparing (edit: that's duplicate output.... all the zeroes...)


EDIT: I've been doing many unnecessary conversions and I believe there are still some in there, but at least the packet looks good for the first time. But I still think there is something wrong with the sending itself and I totally don't get the receiving.. No file is created.
 
Zuletzt bearbeitet:
@PeterPawn: Would you mind to have another look?
 
It's an exciting time of the year ... I've never seen AVM releasing firmware versions on such a tight schedule.

I didn't forget this problem and I swear an oath ... I'll be back. :) Soon. :) Not yet. :)

I did not look into your changes ... but I've read something about "using od" and so on. Afaik the yf_functions handle the whole content as a hexadecimal string until it's converted with yf_hex2bin. That means, it should be unnecessary to revert this conversion to check the content of the created packet (as long as you rely on the correct work of yf_hex2bin), you should be able to print the "raw" content of the created packet as a debug output in front of each "yf_hex2bin" call (and even later, if you do not overwrite the used variables with other values).

I'm unsure, if a MAC address is really a problem for yf_pack ... as long as you've got the address, you need only to remove the contained dashes or colons (if any exist) and afterwards you can use the remaining hexadecimal string in any concatenation with other strings forming your packet to send. Because this data is already in hex and all other data is converted to hex (and in the last step to binary), I can't see any useful additional conversion for "yf_pack" - this would be something like a "direct copy from input to output" action and in my opinion, this isn't really useful. It's always better to keep the created strings with hex data as short as possible, because string handling in POSIX compatible shell code is really a pain (somewhere in the back) and exceptionally slow.

- - - Aktualisiert - - -

BTW ... please add a complete version of the current script in a new writing - I'm mostly unable to differentiate between your comments added later and below the script and meanwhile applied changes to the (prior embedded) code block.

I bet, there aren't too many co-readers, it should not really bother anyone, if you repeat any code blocks and it should even be easier (and less error-prone) to copy the whole script into a new writing.
 
Thanks Peter,

I'm using a mix of functions to construct the packet to send.
I think I'm close....
I've added "od" to inspect the packet I want to send. It's not really part of the procedure.

I'm supposing the answer is sent with a multicast packet to 255.255.255.255 port 68, but I'm not certain of that.
I therefore wanted to send the discovery packet from port 68 as well, but this is not possible if I'm already listening to that packet.
Is it not a problem to send the discovery from an arbitrary port?
Will the DHCPOFFER still be sent to port 68?

The only data I need from the DHCPOFFER packet is the server.
Maybe there is no need to actually read the packet???


I've now added more comments in my code.

cat make/zabbix_agentd-support/files/root/sbin/roguedhcp
Code:
#!/bin/sh


# find rogue DHCP-server
#
# I'm using some the functions that should be available on your development box.
# They should be present on your system
# You should be able to find them using the command
#                    find -type f -name yf_fritzos_model_settings.function
#
# I'm adding the complete folder "functions" by mounting that folder to a folder I created in ~/trunk/make/zabbix_agentd-support/files/root/
#
# sudo mount --bind ~/trunk/source/host-tools/yourfritz-2abe5f1fa8/helpers/functions ~/trunk/make/zabbix_agentd-support/files/root/functions
#
# Maybe it would have worked with a symlink as well, but I didn't want to go through a complete compile / flash cycle to find out it doesn't
#
# In this folder are several functions that are sourced at the beginning of the script.
# The functions in the file "yf_fritzos_model_settings.function" should be skipped at first and executed afterwards (if it's a Fritzbox)
#
#
# The script sends a "DHCP discovery" packet to 255.255.225.255:67 using UDP and will listen for a "DHCP offer"
# If it gets replies from other DHCP-servers than the ones filtered out (for instance itself), it will return these IP's seperated by a space.
# If all is good and only valid DHCP-servers are found, it will return a dot (a Zabbix thing).
#
# I'm placing this file in ~/trunk/make/zabbix_agentd-support/files/root/sbin/ as "roguedhcp"
#
# mount /home/freetz/trunk/source/host-tools/yourfritz-2abe5f1fa8/helpers/functions /home/freetz/trunk/make/zabbix_agentd-support/files/root/functions
#
#
# https://en.wikipedia.org/wiki/Dynamic_Host_Configuration_Protocol
#


# source all yf_* functions from folder /functions
for function in /functions/yf_* ; do
  echo "${function}" | grep -q yf_fritzos_model_settings.function && continue
  . $function
done
yf_is_fritzos_device && . /functions/yf_fritzos_model_settings.function


IFACE=lan
PROG="$0"
[ "${PROG}" = "-sh" ] && PROG="prompt"      # make it possible to execute the commands in this script one by one from the prompt


TMPDIR=`mktemp -p /var/tmp -d ${PROG//*\/}.XXXXXXXXXX`  # create a tempfolder in /var/tmp
VALID_DHCP=${TMPDIR}/valid_dhcp
FOUND_DHCP=${TMPDIR}/found_dhcp
ROGUE_DHCP=${TMPDIR}/rogue_dhcp


HEADLESS=                                   # HEADLESS is undefined if this script is executed from the prompt
tty >/dev/null || HEADLESS=true             # It can be used to show debugging data


SUDO=`which sudo`
[ "${USER}" == 'root' ] && SUDO=''          # If the script is executed as user "zabbix" it will use sudo for socat
SOCAT="${SUDO} socat"                       # Linux doesn't like it when using sudo as root


FREETZ_IP=`ifconfig ${IFACE} 2>/dev/null | grep -o 'inet addr:[0-9.]*' | awk -F: '{print $2}'`    # get primary LAN IP
SECOND_IP=`ifconfig ${IFACE}:0 2>/dev/null | grep -o 'inet addr:[0-9.]*' | awk -F: '{print $2}'`  # get secondary LAN IP (169.254.1.1)
FREETZ_MAC="`ifconfig lan | grep -o 'HWaddr.*' | awk '{print $2}' | tr -d ':'`"                   # get the LAN's MAC-address


DEFAULT_WAIT=1


BOOTP_SERVER_PORT=67  # port DHCP-server
BOOTP_CLIENT_PORT=68  # port DHCP-client


#  discover function
discoverdhcp()
{


  # prepare discovery packet
  Op=1
  HType=1
  HLen=6
  Hops=0
  Secs=0
  Flags=32768           # 1000 0000


  head -c4 /dev/urandom >${TMPDIR}/xid     # A random 4-bytes (I may need it later??)


  # The packet buffer file
  DISCOVER_PACKETFILE="${TMPDIR}/discover.packet"


  # Construct the packet by using yf_pack, yf_hex2bin, cat & head -cXX < /dev/zero
  yf_pack 8 $Op 8 $HType 8 $HLen 8 $Hops >${DISCOVER_PACKETFILE}  # discovery header
  cat ${TMPDIR}/xid                      >>${DISCOVER_PACKETFILE} # XID 4 random octets
  yf_pack 16 $Secs 16 $Flags             >>${DISCOVER_PACKETFILE} # secs and flags
  head -c16                  < /dev/zero >>${DISCOVER_PACKETFILE} # 4 x 4 zeroes
  yf_hex2bin ${FREETZ_MAC}               >>${DISCOVER_PACKETFILE} # mac-address
  head -c202                 < /dev/zero >>${DISCOVER_PACKETFILE} # 202 zeroes
  yf_pack 8 99 8 130 8 83 8 99 8 1       >>${DISCOVER_PACKETFILE} # magic cookie and discovery mark
  head -c71                  < /dev/zero >>${DISCOVER_PACKETFILE} # 81 zeroes


  if ! [ ${HEADLESS} ] ; then            # show what the packet looks like if executed from prompt
    echo -e "I constructed a packet of `stat -c%s ${DISCOVER_PACKETFILE}` bytes containing:\n\n" >&2
    od -x ${DISCOVER_PACKETFILE} >&2
  fi


  MAIN=$$


  # Open listening socket on port 68 of the secondary interface and save to ${TMPDIR}/response
  $SOCAT UDP4-LISTEN:${BOOTP_CLIENT_PORT},bind=${SECOND_IP} FILE:${TMPDIR}/response,creat &
  LISTENER=$!


  # Open writing socket to 255.255.255.255:67 of the secondary interface and use ${TMPDIR}/broadcast as input
  $SOCAT PIPE:${TMPDIR}/broadcast,ignoreeof UDP4-DATAGRAM:255.255.255.255:${BOOTP_SERVER_PORT},broadcast,bind=${SECOND_IP} &
  BROADCASTER=$!


  cat ${DISCOVER_PACKETFILE} >${TMPDIR}/broadcast
  [ -d /proc/${BROADCASTER} ] && kill ${BROADCASTER} 2>/dev/null 1>&2
  sleep 1


  [ -d /proc/${LISTENER} ]    && kill ${LISTENER} 2>/dev/null 1>&2
  wait ${LISTENER}


  if ! [ ${HEADLESS} ] ; then
    echo -e "I found an answer of `stat -c%s ${TMPDIR}/response` bytes containing:\n\n" >&2
    od -x ${TMPDIR}/response >&2
  fi


  # From here on it's messy and don't really know what to do.
  # I think the DHCP-packet hasn't been sent properly, really


  RESPONSE=$(cat ${TMPDIR}/response 2>/dev/null | yf_bin2hex)
  if [ ${#RESPONSE} -gt 0 -a x${RESPONSE:8:8} == x02000000 ]; then
    # we assume, that the LE value of 2 above represents an answer and
    # if it's found, we assume the next 4 bytes are the IPv4 address
    # in LE format
    IP=$(yf_print_ip ${RESPONSE:22:2}${RESPONSE:20:2}${RESPONSE:18:2}${RESPONSE:16:2})
    [ ${HEADLESS} ] || echo "$IP"
  fi
}


# Take a (list of) IP's to be considered valid DHCP-servers
echo "$*" | egrep -o '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' >${VALID_DHCP}


if netstat -anu | grep -q '0\.0\.0\.0:67' ; then # check if DHCP is running
  # add Freetz's IP to ignore
  echo ${FREETZ_IP} >>${VALID_DHCP}
fi


# The typical Windows SBS has its IP on 192.168.16.2
# It makes things much easier for me to hardcode these instead of passing it
echo '192.168.16.2
192.168.16.4' >>${VALID_DHCP}


[ ${HEADLESS} ] || echo "Running $0 ignoring these IP's I assume are valid `cat ${VALID_DHCP} | tr '\r\n' ' '`" >&2


# convert file with DHCP-server IP's to be used as a grep filter (place caret at start, $ at end and put backslashes in front of the dots)
sed -i 's/\./\\./g;s/.*/^&\$/g' ${VALID_DHCP}


# Execute it more times quickly in the hope that I can also catch the rogue DHCP-server
discoverdhcp  >>${FOUND_DHCP}


[ ${HEADLESS} ] || echo "Found these DHCP-servers: `sort ${FOUND_DHCP} | uniq | tr '\r\n' ' '`" >&2


sort ${FOUND_DHCP} | uniq | grep -vf ${VALID_DHCP} >${ROGUE_DHCP}


if [ ! -s ${ROGUE_DHCP} ] ; then
  # Zabbix needs a parameter or it will become unsupported. A dot is sent
  echo '.'
  [ ${HEADLESS} ] || echo "No rogue DHCP-servers found" >&2
else
  # when passed as a string to Zabbix it can be used for compares. A list can't
  cat ${ROGUE_DHCP} | tr '\r\n' ' '
  [ ${HEADLESS} ] || echo "Found these rogue DHCP-servers: `cat ${ROGUE_DHCP} | tr '\r\n' ' '`" >&2
fi


rm -rf ${TMPDIR}  # remove temporary directory
 
I didn't look into your script yet ... only a quick comment/proposal regarding the port question from the text in front of it.

Maybe you need the "-u" or "-U" option of "socat" here (it should limit the socket open to "read" or "write" (not both), but I didn't check it myself) ... another approach could be the "reuseaddr" option from the "SOCKET" group. Using UDP it should perfectly work to "share" a port number.
 
I didn't look into your script yet ... only a quick comment/proposal regarding the port question from the text in front of it.

Maybe you need the "-u" or "-U" option of "socat" here (it should limit the socket open to "read" or "write" (not both), but I didn't check it myself) ... another approach could be the "reuseaddr" option from the "SOCKET" group. Using UDP it should perfectly work to "share" a port number.

The "-u" didn't make a difference, but the "reuseaddr" did.

Somehow no ${TMPDIR}/response file is created.
Maybe it only does this when it receives something???
I can imagine nothing's coming in yet.

I've added more debugging code....

Code:
# cat /tmp/roguedhcp
#!/bin/sh


# find rogue DHCP-server
#
# I'm using some the functions that should be available on your development box.
# They should be present on your system
# You should be able to find them using the command
#                    find -type f -name yf_fritzos_model_settings.function
#
# I'm adding the complete folder "functions" by mounting that folder to a folder I created in ~/trunk/make/zabbix_agentd-support/files/root/
#
# sudo mount --bind ~/trunk/source/host-tools/yourfritz-2abe5f1fa8/helpers/functions ~/trunk/make/zabbix_agentd-support/files/root/functions
#
# Maybe it would have worked with a symlink as well, but I didn't want to go through a complete compile / flash cycle to find out it doesn't
#
# In this folder are several functions that are sourced at the beginning of the script.
# The functions in the file "yf_fritzos_model_settings.function" should be skipped at first and executed afterwards (if it's a Fritzbox)
#
#
# The script sends a "DHCP discovery" packet to 255.255.225.255:67 using UDP and will listen for a "DHCP offer"
# If it gets replies from other DHCP-servers than the ones filtered out (for instance itself), it will return these IP's seperated by a space.
# If all is good and only valid DHCP-servers are found, it will return a dot (a Zabbix thing).
#
# I'm placing this file in ~/trunk/make/zabbix_agentd-support/files/root/sbin/ as "roguedhcp"
#
# mount /home/freetz/trunk/source/host-tools/yourfritz-2abe5f1fa8/helpers/functions /home/freetz/trunk/make/zabbix_agentd-support/files/root/functions
#
#
# https://en.wikipedia.org/wiki/Dynamic_Host_Configuration_Protocol
#


# source all yf_* functions from folder /functions
for function in /functions/yf_* ; do
  echo "${function}" | grep -q yf_fritzos_model_settings.function && continue
  . $function
done
yf_is_fritzos_device && . /functions/yf_fritzos_model_settings.function


IFACE=lan
PROG="$0"
[ "${PROG}" = "-sh" ] && PROG="prompt"      # make it possible to execute the commands in this script one by one from the prompt


HEADLESS=                                   # HEADLESS is undefined if this script is executed from the prompt
tty >/dev/null || HEADLESS=true             # It can be used to show debugging data


SUDO=`which sudo`
SOCAT=`which socat`
if [ -z "${SOCAT}" ] ; then
  echo "I'm missing the binary socat, I can't continue" >&2
  exit 1
fi
[ "${USER}" == 'root' ] && SUDO=''          # If the script is executed as user "zabbix" it will use sudo for socat
SOCAT="${SUDO} ${SOCAT}"                    # Linux doesn't like it when using sudo as root


TMPDIR=`mktemp -p /var/tmp -d ${PROG//*\/}.XXXXXXXXXX`  # create a tempfolder in /var/tmp
VALID_DHCP=${TMPDIR}/valid_dhcp
FOUND_DHCP=${TMPDIR}/found_dhcp
ROGUE_DHCP=${TMPDIR}/rogue_dhcp


FREETZ_IP=`ifconfig ${IFACE} 2>/dev/null | grep -o 'inet addr:[0-9.]*' | awk -F: '{print $2}'`    # get primary LAN IP
SECOND_IP=`ifconfig ${IFACE}:0 2>/dev/null | grep -o 'inet addr:[0-9.]*' | awk -F: '{print $2}'`  # get secondary LAN IP (169.254.1.1)
FREETZ_MAC="`ifconfig lan | grep -o 'HWaddr.*' | awk '{print $2}' | tr -d ':'`"                   # get the LAN's MAC-address


DEFAULT_WAIT=1


BOOTP_SERVER_PORT=67  # port DHCP-server
BOOTP_CLIENT_PORT=68  # port DHCP-client


#  discover function
discoverdhcp()
{


  # prepare discovery packet
  Op=1
  HType=1
  HLen=6
  Hops=0
  Secs=0
  Flags=32768           # 1000 0000


  head -c4 /dev/urandom >${TMPDIR}/xid     # A random 4-bytes (I may need it later??)


  # The packet buffer file
  DISCOVER_PACKETFILE="${TMPDIR}/discover.packet"


  # Construct the packet by using yf_pack, yf_hex2bin, cat & head -cXX < /dev/zero
  yf_pack 8 $Op 8 $HType 8 $HLen 8 $Hops >${DISCOVER_PACKETFILE}  # discovery header
  cat ${TMPDIR}/xid                      >>${DISCOVER_PACKETFILE} # XID 4 random octets
  yf_pack 16 $Secs 16 $Flags             >>${DISCOVER_PACKETFILE} # secs and flags
  head -c16                  < /dev/zero >>${DISCOVER_PACKETFILE} # 4 x 4 zeroes
  yf_hex2bin ${FREETZ_MAC}               >>${DISCOVER_PACKETFILE} # mac-address
  head -c202                 < /dev/zero >>${DISCOVER_PACKETFILE} # 192 zeroes
  yf_pack 8 99 8 130 8 83 8 99 8 1       >>${DISCOVER_PACKETFILE} # magic cookie and discovery mark
  head -c71                  < /dev/zero >>${DISCOVER_PACKETFILE} # 81 zeroes


  if ! [ ${HEADLESS} ] ; then            # show what the packet looks like if executed from prompt
    echo -e "I constructed a packet of `stat -c%s ${DISCOVER_PACKETFILE}` bytes containing:\n\n" >&2
    od -x ${DISCOVER_PACKETFILE} >&2
  fi


  MAIN=$$


  [ ${HEADLESS} ] || echo "Start listening on ${SECOND_IP}:${BOOTP_CLIENT_PORT}" >&2
  # Open listening socket on port 68 of the secondary interface and save to ${TMPDIR}/response
  $SOCAT UDP4-LISTEN:${BOOTP_CLIENT_PORT},bind=${SECOND_IP} open:${TMPDIR}/response,creat &
  LISTENER=$!
  [ ${HEADLESS} ] || echo "Process# $LISTENER is listening on ${SECOND_IP}:${BOOTP_CLIENT_PORT}" >&2


  [ ${HEADLESS} ] || echo "Open writing socket from ${SECOND_IP}:${BOOTP_CLIENT_PORT} to 255.255.255.255:${BOOTP_SERVER_PORT}" >&2
  # Open writing socket to 255.255.255.255:67 of the secondary interface and use ${TMPDIR}/broadcast as input
  $SOCAT PIPE:${TMPDIR}/broadcast,ignoreeof UDP4-DATAGRAM:255.255.255.255:${BOOTP_SERVER_PORT},broadcast,bind=${SECOND_IP},reuseaddr,sp=${BOOTP_CLIENT_PORT} &
  BROADCASTER=$!
  [ ${HEADLESS} ] || echo "Process# $BROADCASTER will be writing to 255.255.255.255:${BOOTP_SERVER_PORT}" >&2
  sleep 1


  cat ${DISCOVER_PACKETFILE} >${TMPDIR}/broadcast
  [ -d /proc/${BROADCASTER} ] && kill ${BROADCASTER} 2>/dev/null 1>&2
  [ -d /proc/${LISTENER} ]    && kill ${LISTENER} 2>/dev/null 1>&2
  wait ${BROADCASTER}
  wait ${LISTENER}


  if ! [ ${HEADLESS} ] ; then
    echo -e "I found an answer of `stat -c%s ${TMPDIR}/response` bytes containing:\n\n" >&2
    od -x ${TMPDIR}/response >&2
  fi


  # From here on it's messy and don't really know what to do.
  # I think the DHCP-packet hasn't been sent properly, really


  RESPONSE=$(cat ${TMPDIR}/response 2>/dev/null | yf_bin2hex)
  if [ ${#RESPONSE} -gt 0 -a x${RESPONSE:8:8} == x02000000 ]; then
    # we assume, that the LE value of 2 above represents an answer and
    # if it's found, we assume the next 4 bytes are the IPv4 address
    # in LE format
    IP=$(yf_print_ip ${RESPONSE:22:2}${RESPONSE:20:2}${RESPONSE:18:2}${RESPONSE:16:2})
    [ ${HEADLESS} ] || echo "$IP"
  fi
}


# Take a (list of) IP's to be considered valid DHCP-servers
echo "$*" | egrep -o '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' >${VALID_DHCP}


if netstat -anu | grep -q '0\.0\.0\.0:67' ; then # check if DHCP is running
  # add Freetz's IP to ignore
  echo ${FREETZ_IP} >>${VALID_DHCP}
fi


# The typical Windows SBS has its IP on 192.168.16.2
# It makes things much easier for me to hardcode these instead of passing it
echo '192.168.16.2
192.168.16.4' >>${VALID_DHCP}


[ ${HEADLESS} ] || echo "Running $0 ignoring these IP's I assume are valid `cat ${VALID_DHCP} | tr '\r\n' ' '`" >&2


# convert file with DHCP-server IP's to be used as a grep filter (place caret at start, $ at end and put backslashes in front of the dots)
sed -i 's/\./\\./g;s/.*/^&\$/g' ${VALID_DHCP}


# Execute it more times quickly in the hope that I can also catch the rogue DHCP-server
discoverdhcp  >>${FOUND_DHCP}


[ ${HEADLESS} ] || echo "Found these DHCP-servers: `sort ${FOUND_DHCP} | uniq | tr '\r\n' ' '`" >&2


sort ${FOUND_DHCP} | uniq | grep -vf ${VALID_DHCP} >${ROGUE_DHCP}


if [ ! -s ${ROGUE_DHCP} ] ; then
  # Zabbix needs a parameter or it will become unsupported. A dot is sent
  echo '.'
  [ ${HEADLESS} ] || echo "No rogue DHCP-servers found" >&2
else
  # when passed as a string to Zabbix it can be used for compares. A list can't
  cat ${ROGUE_DHCP} | tr '\r\n' ' '
  [ ${HEADLESS} ] || echo "Found these rogue DHCP-servers: `cat ${ROGUE_DHCP} | tr '\r\n' ' '`" >&2
fi


rm -rf ${TMPDIR}  # remove temporary directory
 
@PeterPawn

I hope things are less rural than they were 2 months ago and you're able to find some time to help me move forward.
I'm still in need of this and hope you can help me taking some hurdles to get there.

In the mean time I've also started to use version 6.8 international on some 7490's
By doing so I've lost the ability to monitor the DSL-parameters and can't see if a line's DSL-speed is changed or the line is saturated.

If you could take a look at this thread as well you would make my day
http://www.ip-phone-forum.de/showthread.php?t=275094
 
Holen Sie sich 3CX - völlig kostenlos!
Verbinden Sie Ihr Team und Ihre Kunden Telefonie Livechat Videokonferenzen

Gehostet oder selbst-verwaltet. Für bis zu 10 Nutzer dauerhaft kostenlos. Keine Kreditkartendetails erforderlich. Ohne Risiko testen.

3CX
Für diese E-Mail-Adresse besteht bereits ein 3CX-Konto. Sie werden zum Kundenportal weitergeleitet, wo Sie sich anmelden oder Ihr Passwort zurücksetzen können, falls Sie dieses vergessen haben.