How to write a standalone URL logger for Windows? - network-programming

I want to write a program to log all the URLs visited on a computer, but standalone, so not as a Fiddler2 extension. Are there any libraries out there that do this already that I could include in my application (which I was intending to write in C# .Net, but I'm flexible as long as it's for Windows)? If not are there any that could at least facilitate reading information from HTTP packets? I want to analyse the urls on the fly. Thanks.

For that, you would have to sniff the traffic on the computer under analysis.
To achieve this, use pcap library. As you may want to use a higher level programming language as C# (or Java), there are a lot of wrappers available to facilitate the usage of pcap library. In Java (since I am more used to it), there is one wrapper called jNetPcap. It is open source and has a good documentation. See the example below to sniff the traffic of any of our NICs:
package org.jnetpcap.examples;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.jnetpcap.Pcap;
import org.jnetpcap.PcapIf;
import org.jnetpcap.packet.PcapPacket;
import org.jnetpcap.packet.PcapPacketHandler;
/**
* Here is the output generated by this example :
*
* Network devices found:
* #0: \Device\NPF_{BC81C4FC-242F-4F1C-9DAD-EA9523CC992D} [Intel(R) PRO/100 VE]
* #1: \Device\NPF_{E048DA7F-D007-4EEF-909D-4238F6344971} [VMware Virtual Ethernet Adapter]
* #2: \Device\NPF_{5B62B373-3EC1-460D-8C71-54AA0BF761C7} [VMware Virtual Ethernet Adapter]
* #3: \Device\NPF_GenericDialupAdapter [Adapter for generic dialup and VPN capture]
*
* Choosing 'Intel(R) PRO/100 VE) ' on your behalf:
* Received packet at Tue Nov 03 18:52:42 EST 2009 caplen=1362 len=1362 jNetPcap rocks!
* Received packet at Tue Nov 03 18:52:45 EST 2009 caplen=82 len=82 jNetPcap rocks!
* Received packet at Tue Nov 03 18:52:45 EST 2009 caplen=145 len=145 jNetPcap rocks!
* Received packet at Tue Nov 03 18:52:45 EST 2009 caplen=62 len=62 jNetPcap rocks!
* Received packet at Tue Nov 03 18:52:45 EST 2009 caplen=164 len=164 jNetPcap rocks!
* Received packet at Tue Nov 03 18:52:45 EST 2009 caplen=62 len=62 jNetPcap rocks!
* Received packet at Tue Nov 03 18:52:45 EST 2009 caplen=54 len=54 jNetPcap rocks!
* Received packet at Tue Nov 03 18:52:45 EST 2009 caplen=1073 len=1073 jNetPcap rocks!
* Received packet at Tue Nov 03 18:52:45 EST 2009 caplen=1514 len=1514 jNetPcap rocks!
* Received packet at Tue Nov 03 18:52:45 EST 2009 caplen=279 len=279 jNetPcap rocks!
*/
public class ClassicPcapExample {
/**
* Main startup method
*
* #param args
* ignored
*/
public static void main(String[] args) {
List<PcapIf> alldevs = new ArrayList<PcapIf>(); // Will be filled with NICs
StringBuilder errbuf = new StringBuilder(); // For any error msgs
/***************************************************************************
* First get a list of devices on this system
**************************************************************************/
int r = Pcap.findAllDevs(alldevs, errbuf);
if (r == Pcap.NOT_OK || alldevs.isEmpty()) {
System.err.printf("Can't read list of devices, error is %s", errbuf
.toString());
return;
}
System.out.println("Network devices found:");
int i = 0;
for (PcapIf device : alldevs) {
String description =
(device.getDescription() != null) ? device.getDescription()
: "No description available";
System.out.printf("#%d: %s [%s]\n", i++, device.getName(), description);
}
PcapIf device = alldevs.get(0); // We know we have atleast 1 device
System.out
.printf("\nChoosing '%s' on your behalf:\n",
(device.getDescription() != null) ? device.getDescription()
: device.getName());
/***************************************************************************
* Second we open up the selected device
**************************************************************************/
int snaplen = 64 * 1024; // Capture all packets, no trucation
int flags = Pcap.MODE_PROMISCUOUS; // capture all packets
int timeout = 10 * 1000; // 10 seconds in millis
Pcap pcap =
Pcap.openLive(device.getName(), snaplen, flags, timeout, errbuf);
if (pcap == null) {
System.err.printf("Error while opening device for capture: "
+ errbuf.toString());
return;
}
/***************************************************************************
* Third we create a packet handler which will receive packets from the
* libpcap loop.
**************************************************************************/
PcapPacketHandler<String> jpacketHandler = new PcapPacketHandler<String>() {
public void nextPacket(PcapPacket packet, String user) {
System.out.printf("Received packet at %s caplen=%-4d len=%-4d %s\n",
new Date(packet.getCaptureHeader().timestampInMillis()),
packet.getCaptureHeader().caplen(), // Length actually captured
packet.getCaptureHeader().wirelen(), // Original length
user // User supplied object
);
}
};
/***************************************************************************
* Fourth we enter the loop and tell it to capture 10 packets. The loop
* method does a mapping of pcap.datalink() DLT value to JProtocol ID, which
* is needed by JScanner. The scanner scans the packet buffer and decodes
* the headers. The mapping is done automatically, although a variation on
* the loop method exists that allows the programmer to sepecify exactly
* which protocol ID to use as the data link type for this pcap interface.
**************************************************************************/
pcap.loop(10, jpacketHandler, "jNetPcap rocks!");
/***************************************************************************
* Last thing to do is close the pcap handle
**************************************************************************/
pcap.close();
}
}
This example was extracted from the jNetPcap website. As you can see, you just have to customize the nextPacket() method to make what you are intending.
That would simply be:
public void nextPacket(PcapPacket packet, String user) {
Http http = new Http();
if (packet.hasHeader(http)) {
System.out.printf("Received packet at %s: %s\n",
new Date(packet.getCaptureHeader().timestampInMillis()),
http.Request.valueOf("Host")
);
}
Hope I've helped.

Related

How to extend Dart sockets BroadcastStream buffer size of 1024 bytes?

IMPORTANT EDIT
After further investigating, I found out that the packet size is in fact much larger than the stated 1024 bytes, the 1024 bytes were just the limit of the standard out I was using (android studio / flutter).
Some of the packets received are now up to ~27 000 bytes large, however that is nowhere near the actually transmitted size of over 10x that.
I am trying to send singular packets of up to 5 MB in length over a Socket connection in Dart. For this, I am using the following code:
Socket socket = await Socket.connect(globals.serverUrl, globals.serverPort);
Stream<Uint8List> stream = socket?.asBroadcastStream();
Uint8List? response = await stream?.first;
String responseString = String.fromCharCodes(response);
Note that my Server is running Java while the Client is using Dart.
After sending the data packet from the Server to the Client, it successfully receives the first 1024 bytes exactly of the packet, and the rest is nowhere to be found, even after reading stream.first multiple times, they continuously read the newly sent packet and not the remaining bytes of the old packet.
So my question is, how do I require the Socket stream to read ALL bytes of the packet until finished, and not just the first 1024?
EDIT:
The received packet on the client is parsed using:
String? decrypt(String cipherText, String keyString) {
final key = Key.fromUtf8(keyString);
final iv = IV.fromBase64(cipherText.split(":")[1]);
final encrypter = Encrypter(AES(key, mode: AESMode.cbc, padding: null));
final encrypted = Encrypted.fromBase64(cipherText.split(":")[0]);
final decrypted = encrypter.decrypt(encrypted, iv: iv);
globals.log.i("DECRYPTED: $decrypted");
return decrypted;
}
The error that I am getting stems from getting the IV, since the message is cut off at 1024 bytes and the ":" appears much later in the String.
The problem is that the Dart socket split messages bigger than 1024 bytes into multiple packets of 1024 bytes. So there's some approaches you can use to combine them together in the client:
By extending Socket class
I do not believe this is a right solution:
Hard to extend since it's a platform implementation (you can see the sdk implementation of dart:io almost any class method is external).
Hard to maintain.
Since it depends on custom platform implementations you need to do it on multiple platforms.
It's easy to create undocumented memory leaks.
Let me know if you still prefer this approach I'll do a further research.
By using Stream<T>.reduce function
The problem with this approach in your context is that Sockets do not emit a done event when a message is sent by using socket.write('Your message').
So unless you're using a socket to send a single message this function can't help you cause it will return a Future<T> that will never complete (only when the socket connection is closed).
By emitting a EOF message from the server
This is a solution I found even not so elegant, improvements are welcome.
The idea is to concatenate all client received packets into a single one and stop receiving when a pre-determined termination (EOF) string is received.
Implementation
Below is a server implementation that emits a message of 5mb followed by a message:end string every time a new client is connected.
import 'dart:io';
Future<void> main() async {
final ServerSocket serverSocket =
await ServerSocket.bind(InternetAddress.anyIPv4, 5050);
final Stream<Socket> serverStream = serverSocket.asBroadcastStream();
serverStream.listen((client) async {
print(
'New client connected: ${client.address}:${client.port} ${client.done} Remote address: ${client.remoteAddress}');
const int k1byte = 8;
const int k2bytes = k1byte * 2;
const int k1kb = k1byte * 1000;
const int k1mb = k1kb * 1000;
const int k5mb = k1mb * 5;
// Create a 5mb string that follows: '1000.....0001'
final String k1mbMessage = '1${createStringOf(k5mb - k2bytes, '0')}1';
client.write(k1mbMessage);
client.write('message:end');
});
print('Listening on: ${serverSocket.address} ${serverSocket.port}');
}
String createStringOf(int size, [String char = ' ']) {
// https://api.dart.dev/stable/2.17.3/dart-core/String-class.html it says:
// > A sequence of UTF-16 code units.
// And from https://www.ibm.com/docs/en/db2-for-zos/12?topic=unicode-utfs says:
// > UTF-16 is based on 16-bit code units. Each character is encoded as at least 2 bytes.
int dartStringEncodingSize = 2;
assert(size >= dartStringEncodingSize && size.isEven,
'''Dart char contains 2 bytes so we can only create Strings (with exact size) of even N bytes''');
assert(char.length == 1, '''[char] must be a single char String''');
int charCount = size ~/ dartStringEncodingSize;
return char * charCount;
}
And here we can see a client implementation where we use 'our own reduce' function that combine all packets while the termination string is not found.
import 'dart:io';
Future<void> main() async {
final Socket server = await Socket.connect('localhost', 5050);
final Stream<String> serverSocket =
server.asBroadcastStream().map(String.fromCharCodes); // Map to String by default
const kMessageEof = 'message:end';
String message = '';
await for (String packet in serverSocket) {
// If you are using [message] as a List of bytes (Uint8List):
// message = [...Uint8List.fromList(message), ...Uint8List(packet)]
message += packet;
// Do not compare directly packet == kMessageEof
// cause it can be 'broken' into multiple packets:
// -> 00000 (packet 1)
// -> 00000 (packet 2)
// -> 00mes (packet 3)
// -> sage: (packet 4)
// -> end (packet 5)
if (message.endsWith(kMessageEof)) {
// remove termination string
message = message.replaceRange(
message.length - kMessageEof.length,
message.length,
'',
);
}
print('Received: $message'); // Prints '1000000......0000001'
}
}
You can make it more generic if you want by using an extension:
import 'dart:io';
/// This was created since the native [reduce] says:
/// > When this stream is done, the returned future is completed with the value at that time.
///
/// The problem is that socket connections does not emits the [done] event after
/// each message but after the socket disconnection.
///
/// So here is a implementation that combines [reduce] and [takeWhile].
extension ReduceWhile<T> on Stream<T> {
Future<T> reduceWhile({
required T Function(T previous, T element) combine,
required bool Function(T) combineWhile,
T? initialValue,
}) async {
T initial = initialValue ?? await first;
await for (T element in this) {
initial = combine(initial, element);
if (!combineWhile(initial)) break;
}
return initial;
}
}
Future<void> main() async {
final Socket server = await Socket.connect('localhost', 5050);
final Stream<String> serverSocket =
server.asBroadcastStream().map(String.fromCharCodes);
const kMessageEof = 'message:end';
// Reduce with a condition [combineWhile]
String message = await serverSocket.reduceWhile(
combine: (previous, element) => '$previous$element',
combineWhile: (message) => !message.endsWith(kMessageEof),
);
// Remove termination string
message = message.replaceRange(
message.length - kMessageEof.length,
message.length,
'',
);
print('Received: $message');
}
Since the socket itself doesn't send the done event the way I found to reduce all packets into a single one was by emitting 'our own done event'.

IBM HP5Si Print Stream to XPS print driver

I am hoping someone can have suggestions about this issue.
We have a custom driver taken from https://learn.microsoft.com/en-us/samples/microsoft/windows-driver-samples/xpsdrv-driver-and-filter-sample/
The print driver works well and outputs XPS when the documents are opened in MS word or PDF. But when a document is printed from HP5Si series printer, the driver returns 0 bytes. The job is sent from HP5Si printer to the XPS driver. Why is the driver rejecting this input when the source is a HP series printer. What can I do to fix it?
The printer on the AS400 is setup with an IBM HP5Si driver and sends the job to a windows service on a server. This windows service routes the job to XPS driver as if it were an HP series printer. The XPS driver processes this job and returns XPS to the windows service. The windows service then converts to a tiff file.
For some reason if printing is done using this workflow XPS driver returns 0.
If the same document is opened in word or notepad or any not AS400+ HP, it works and XPS is returned.
To prove my theory, I sent a PCL file in C# code to the driver and it returned 0 bytes.
public static void SendBytesToPrinterPCL(string printerName, string szFileName) {
IntPtr lhPrinter;
OpenPrinter(printerName, out lhPrinter, new IntPtr(0));
if (lhPrinter.ToInt32() == 0) return; //Printer not found!!
var rawPrinter = new DOCINFOA() {
pDocName = "My Document",
pDataType = "RAW"
};
StartDocPrinter(lhPrinter, 1, rawPrinter);
using(var b = new BinaryReader(File.Open(szFileName, FileMode.Open))) {
var length = (int) b.BaseStream.Length;
const int bufferSize = 8192;
var numLoops = length / bufferSize;
var leftOver = length % bufferSize;
for (int i = 0; i < numLoops; i++) {
var buffer = new byte[bufferSize];
int dwWritten;
b.Read(buffer, 0, bufferSize);
IntPtr unmanagedPointer = Marshal.AllocHGlobal(buffer.Length);
Marshal.Copy(buffer, 0, unmanagedPointer, buffer.Length);
WritePrinter(lhPrinter, unmanagedPointer, bufferSize, out dwWritten);
Marshal.FreeHGlobal(unmanagedPointer);
}
if (leftOver > 0) {
var buffer = new byte[leftOver];
int dwWritten;
b.Read(buffer, 0, leftOver);
IntPtr unmanagedPointer = Marshal.AllocHGlobal(buffer.Length);
Marshal.Copy(buffer, 0, unmanagedPointer, buffer.Length);
WritePrinter(lhPrinter, unmanagedPointer, leftOver, out dwWritten);
Marshal.FreeHGlobal(unmanagedPointer);
}
}
EndDocPrinter(lhPrinter);
ClosePrinter(lhPrinter);
}
string filePath = #"C:\Users\tom\Desktop\form.PCL";
string szPrinterName = #"\\server\xpsdrv";
Print.SendBytesToPrinterPCL(szPrinterName, filePath);
Then I sent a regular text file to the driver and it successfully converted to XPS.
public static void SendToPrinterNonPCL(string filePath)
{
ProcessStartInfo info = new ProcessStartInfo();
info.Verb = "print";
info.FileName = filePath;
info.CreateNoWindow = true;
info.WindowStyle = ProcessWindowStyle.Hidden;
Process p = new Process();
p.StartInfo = info;
p.Start();
p.WaitForInputIdle();
System.Threading.Thread.Sleep(3000);
if (false == p.CloseMainWindow())
p.Kill();
}
string filePath = #"C:\Users\tom\Desktop\form.txt";
string szPrinterName = #"\\server\xpsdrv";
Print.SendToPrinterNonPCL(filePath);
Why doesn't the driver in Microsoft samples accept PCL? What should I do. I am not a driver developer. This project was given to me.
EDIT:
Initally I didn't know of this printing from AS400. Our legacy driver was built 15 years back. The developer wrote a custom print driver to PCL and a Custom converter to Tiff. But the driver only supported monochrome. I am not a driver expert or a PCL expert or a converter expert. In order to support color and less pixelated feel for the final Tiff, I decided to change it to a XPS driver. Also it is less custom code and could use Microsoft's XPS conversion in WPF. It is not a very big learning curve for a non-driver development person compared to learning PCL and then changing the converter to accomodate color Tiff. But I guess it is falling apart since the users also print from AS400 which sends PCL.
Do you know any good products which we could purchase a license to? We need a PCL driver and a converter to Tiff
Thank you

dart query Steam Master Server

I am trying to get the server IPs from a steam master server via a query and later query the game servers.
https://developer.valvesoftware.com/wiki/Master_Server_Query_Protocol#
There are plenty of examples in different programming languages but not dart.
https://gist.github.com/koenbollen/645947/09241258c0d2dd8d5a4e647865730ab83955f68b#file-mastersteam-py-L18
My problem is: i don't even understand why the address "hl2master.steampowered.com" is an invalid internet address. It says so on the official valve Master Server Query (link at top of the post).
This is the error i get trying to run the code:
[ERROR:flutter/lib/ui/ui_dart_state.cc(148)] Unhandled Exception: Invalid argument(s): Invalid internet address hl2master.steampowered.com
import 'dart:io';
import 'dart:convert';
// Master server:
const GOLD_SRC = "hl1master.steampowered.com";
const GOLD_SRC_PORT = 27010;
const SOURCE = "hl2master.steampowered.com";
const SOURCE_PORT = 27011;
// Regions:
const US_EAST_COAST = 0x00;
const US_WEST_COAST = 0x01;
const SOUTH_AMERICA = 0x02;
const EUROPE = 0x03;
const ASIA = 0x04;
const AUSTRALIA = 0x05;
const MIDDLE_EAST = 0x06;
const AFRICA = 0x07;
const ALL = 0xFF;
class MasterServerQuery {
connectSocket01() async {
var message = "31 FF 30 2E 30 2E 30 2E";
InternetAddress master = InternetAddress(SOURCE);
var masterPort = SOURCE_PORT;
RawDatagramSocket.bind(InternetAddress.anyIPv4, 4096).then((
RawDatagramSocket socket) {
print('UDP Echo ready to receive');
print('${socket.address.address}:${socket.port}');
socket.listen((RawSocketEvent e) {
Datagram d = socket.receive();
if (d == null) return;
String message = "31 FF 30 2E 30 2E 30 2E";
print(
'Datagram from ${d.address.address}:${d.port}: ${message.trim()}');
socket.send(message.codeUnits, master, masterPort);
});
});
}
}
The address is not valid, since it expects a valid ip address and not an hostname, you can provide the ip address or use InternetAddress.lookup.
You code is also wrong in the way that you pass the message you shouldn't pass the codeUnits but the bytes in a way as such:
final byteData = ByteData(8);
byteData.setUint8(0, 0x31);
byteData.setUint8(1, 0xFF);
byteData.setUint8(2, 0x30);
byteData.setUint8(3, 0x2E);
byteData.setUint8(4, 0x30);
byteData.setUint8(5, 0x2E);
byteData.setUint8(6, 0x30);
byteData.setUint8(7, 0x2E);
byteData.buffer.asUint8List() //Pass this.

How linux driver can delegate functions to another driver (shared hardware)

I'm writing a custom IR transmitter driver for lirc on an embedded board. The board has a i2c-to-gpio extender (FXL6408).
The problem is that only one GPIO pin is needed for my driver (and hence LIRC) and the other pins are needed for use by other applications. Something like this:
I've read LWM, LDD3, and tons of sites about i2c-slave, i2c adaptors, buses, pinctrl, gpio, stacking etc but its not clear how to do what I want:
my-driver needs to control a single pin on the GPIO extender while still allowing other applications to control the other 7 pins via /dev/i2c-0.
Following this SO suggestion was promising but i2c_new_dummy failed, returning NULL:
i2cAdaptor = i2c_get_adapter(ECP_I2CBUS); // 1 means i2c-1 bus
if (i2cAdaptor == NULL)
{
printk("ecp_gpio: Could not acquire i2c adaptor\n");
return -EBUSY;
}
i2cClient = i2c_new_dummy(i2cAdaptor, ECP_I2CDEV); // 0x43 - slave address on i2c bus
if (i2cClient == NULL)
{
printk("ecp_gpio: Could not acquire i2c client\n");
return -EBUSY;
}
if ( (rc = i2c_smbus_write_byte(i2cClient, 0xF0)) < 0)
{
printk("ecp_gpio: Error writing byte - error %d", rc);
return -EIO;
}
What is the correct way to hook up the plumbing to achieve what I want?
OS Info:
# uname -a
Linux ecp 4.4.127-1.el6.elrepo.i686 #1 SMP Sun Apr 8 09:44:43 EDT 2018 i686 i686 i386 GNU/Linux
After trying many different things, I found one that works. I don't know if it is "the right way" to do it, but it does work.
Instead of trying to create a dummy client, just call the i2c_xx functions directly. So the code looks like:
i2cAdaptor = i2c_get_adapter(ECP_I2CBUS); // 1 means i2c-1 bus etc
if (i2cAdaptor == NULL)
{
printk("ecp_gpio: Could not acquire i2c adaptor\n");
return -EBUSY;
}
union i2c_smbus_data data;
data.byte = 0xF0;
if ((rc = i2c_smbus_xfer(ecpHardware.i2cAdaptor, ECP_I2CDEV, 0, I2C_SMBUS_WRITE, 0x05, I2C_SMBUS_BYTE_DATA, &data)) < 0)
{
printk("ecp_gpio: i2c_smbus_xfer failed: %d\n", rc);
return -EIO;
}
data.byte = 0xE0;
if ((rc = i2c_smbus_xfer(ecpHardware.i2cAdaptor, ECP_I2CDEV, 0, I2C_SMBUS_WRITE, 0x05, I2C_SMBUS_BYTE_DATA, &data)) < 0)
{
printk("ecp_gpio: i2c_smbus_xfer failed: %d\n", rc);
return -EIO;
}

DHCP Option query

I have been looking around and have been trying to figure out how to query the dhcp server I am connected to and get all options available or to at least be able to query with a proper option in mind and get that information in response. Ideally getting all option information from the lease would be great but I really only need it for a specific option that I want to use to package/receive information with.
I have been looking at this in hopes to figure out how to alter it but I am not all that familiar with the D language, making it a cumbersome task. http://blog.thecybershadow.net/2013/01/10/dhcp-test-client/
CODE:
module dhcptest;
import core.thread;
import std.algorithm;
import std.array;
import std.conv;
import std.random;
import std.stdio;
import std.string;
import std.socket;
version(Windows)
import std.c.windows.winsock : ntohs, htons, ntohl, htonl;
else
version(Posix)
import core.sys.posix.netdb : ntohs, htons, ntohl, htonl;
else
static assert(false, "Unsupported platform");
/// Header (part up to the option fields) of a DHCP packet, as on wire.
align(1)
struct DHCPHeader
{
align(1):
/// Message op code / message type. 1 = BOOTREQUEST, 2 = BOOTREPLY
ubyte op;
/// Hardware address type, see ARP section in "Assigned Numbers" RFC; e.g., '1' = 10mb ethernet.
ubyte htype;
/// Hardware address length (e.g. '6' for 10mb ethernet).
ubyte hlen;
/// Client sets to zero, optionally used by relay agents when booting via a relay agent.
ubyte hops;
/// Transaction ID, a random number chosen by the client, used by the client and server to associate messages and responses between a client and a server.
uint xid;
/// Filled in by client, seconds elapsed since client began address acquisition or renewal process.
ushort secs;
/// Flags. (Only the BROADCAST flag is defined.)
ushort flags;
/// Client IP address; only filled in if client is in BOUND, RENEW or REBINDING state and can respond to ARP requests.
uint ciaddr;
/// 'your' (client) IP address.
uint yiaddr;
/// IP address of next server to use in bootstrap; returned in DHCPOFFER, DHCPACK by server.
uint siaddr;
/// Relay agent IP address, used in booting via a relay agent.
uint giaddr;
/// Client hardware address.
ubyte[16] chaddr;
/// Optional server host name, null terminated string.
char[64] sname = 0;
/// Boot file name, null terminated string; "generic" name or null in DHCPDISCOVER, fully qualified directory-path name in DHCPOFFER.
char[128] file = 0;
/// Optional parameters field. See the options documents for a list of defined options.
ubyte[0] options;
static assert(DHCPHeader.sizeof == 236);
}
/*
35 01 02
0F 17 68 6F 6D 65 2E 74 68 65 63 79 62 65 72 73 68 61 64 6F 77 2E 6E 65 74
01 04 FF FF FF 00
06 04 C0 A8 00 01
03 04 C0 A8 00 01
05 04 C0 A8 00 01
36 04 C0 A8 00 01
33 04 00 00 8C A0
FF
*/
struct DHCPOption
{
ubyte type;
ubyte[] data;
}
struct DHCPPacket
{
DHCPHeader header;
DHCPOption[] options;
}
enum DHCPOptionType : ubyte
{
subnetMask = 1,
timeOffset = 2,
router = 3,
timeServer = 4,
nameServer = 5,
domainNameServer = 6,
domainName = 15,
leaseTime = 51,
netbiosNodeType = 46,
dhcpMessageType = 53,
serverIdentifier = 54,
renewalTime = 58,
rebindingTime = 59,
}
enum DHCPMessageType : ubyte
{
discover = 1,
offer ,
request ,
decline ,
ack ,
nak ,
release,
inform
}
enum NETBIOSNodeType : ubyte
{
bNode = 1,
pNode,
mMode,
hNode
}
DHCPPacket parsePacket(ubyte[] data)
{
DHCPPacket result;
enforce(data.length > DHCPHeader.sizeof + 4, "DHCP packet too small");
result.header = *cast(DHCPHeader*)data.ptr;
data = data[DHCPHeader.sizeof..$];
enforce(data[0..4] == [99, 130, 83, 99], "Absent DHCP option magic cookie");
data = data[4..$];
ubyte readByte()
{
enforce(data.length, "Unexpected end of packet");
ubyte result = data[0];
data = data[1..$];
return result;
}
while (true)
{
auto optionType = readByte();
if (optionType==0) // pad option
continue;
if (optionType==255) // end option
break;
auto len = readByte();
DHCPOption option;
option.type = optionType;
foreach (n; 0..len)
option.data ~= readByte();
result.options ~= option;
}
return result;
}
ubyte[] serializePacket(DHCPPacket packet)
{
ubyte[] data;
data ~= cast(ubyte[])((&packet.header)[0..1]);
data ~= [99, 130, 83, 99];
foreach (option; packet.options)
{
data ~= option.type;
data ~= to!ubyte(option.data.length);
data ~= option.data;
}
data ~= 255;
return data;
}
string ip(uint addr) { return format("%(%d.%)", cast(ubyte[])((&addr)[0..1])); }
void printPacket(DHCPPacket packet)
{
auto opNames = [1:"BOOTREQUEST",2:"BOOTREPLY"];
writefln(" op=%s\n chaddr=%(%02X:%)\n hops=%d\n xid=%08X\n secs=%d\n flags=%04X\n ciaddr=%s\n yiaddr=%s\n siaddr=%s\n giaddr=%s\n sname=%s\n file=%s",
opNames.get(packet.header.op, text(packet.header.op)),
packet.header.chaddr[0..packet.header.hlen],
packet.header.hops,
packet.header.xid,
ntohs(packet.header.secs),
ntohs(packet.header.flags),
ip(packet.header.ciaddr),
ip(packet.header.yiaddr),
ip(packet.header.siaddr),
ip(packet.header.giaddr),
to!string(packet.header.sname.ptr),
to!string(packet.header.file.ptr),
);
writefln(" %d options:", packet.options.length);
foreach (option; packet.options)
{
auto type = cast(DHCPOptionType)option.type;
writef(" %s: ", type);
switch (type)
{
case DHCPOptionType.dhcpMessageType:
enforce(option.data.length==1, "Bad dhcpMessageType data length");
writeln(cast(DHCPMessageType)option.data[0]);
break;
case DHCPOptionType.netbiosNodeType:
enforce(option.data.length==1, "Bad netbiosNodeType data length");
writeln(cast(NETBIOSNodeType)option.data[0]);
break;
case DHCPOptionType.subnetMask:
case DHCPOptionType.router:
case DHCPOptionType.timeServer:
case DHCPOptionType.nameServer:
case DHCPOptionType.domainNameServer:
case DHCPOptionType.serverIdentifier:
enforce(option.data.length % 4 == 0, "Bad IP option data length");
writefln("%(%s, %)", map!ip(cast(uint[])option.data).array());
break;
case DHCPOptionType.domainName:
writeln(cast(string)option.data);
break;
case DHCPOptionType.timeOffset:
case DHCPOptionType.leaseTime:
case DHCPOptionType.renewalTime:
case DHCPOptionType.rebindingTime:
enforce(option.data.length % 4 == 0, "Bad integer option data length");
writefln("%(%d, %)", map!ntohl(cast(uint[])option.data).array());
break;
default:
writefln("%(%02X %)", option.data);
}
}
}
enum SERVER_PORT = 67;
enum CLIENT_PORT = 68;
__gshared UdpSocket socket;
void listenThread()
{
try
{
static ubyte[0x10000] buf;
ptrdiff_t received;
Address address;
while ((received = socket.receiveFrom(buf[], address)) > 0)
{
auto receivedData = buf[0..received].dup;
try
{
auto packet = parsePacket(receivedData);
writefln("Received packet from %s:", address);
printPacket(packet);
}
catch (Exception e)
writefln("Error while parsing packet [%(%02X %)]: %s", receivedData, e.toString());
}
throw new Exception(format("socket.receiveFrom returned %d.", received));
}
catch (Exception e)
{
writeln("Error on listening thread:");
writeln(e.toString());
}
}
void sendPacket()
{
DHCPPacket packet;
packet.header.op = 1; // BOOTREQUEST
packet.header.htype = 1;
packet.header.hlen = 6;
packet.header.hops = 0;
packet.header.xid = uniform!uint();
packet.header.flags = htons(0x8000); // Set BROADCAST flag - required to be able to receive a reply to an imaginary hardware address
foreach (ref b; packet.header.chaddr[0..packet.header.hlen])
b = uniform!ubyte();
packet.options ~= DHCPOption(DHCPOptionType.dhcpMessageType, [DHCPMessageType.discover]);
writefln("Sending packet:");
printPacket(packet);
socket.sendTo(serializePacket(packet), new InternetAddress("255.255.255.255", SERVER_PORT));
}
void main()
{
socket = new UdpSocket();
socket.setOption(SocketOptionLevel.SOCKET, SocketOption.BROADCAST, 1);
try
{
socket.setOption(SocketOptionLevel.SOCKET, SocketOption.REUSEADDR, 1);
socket.bind(getAddress("0.0.0.0", CLIENT_PORT)[0]);
writefln("Listening for DHCP replies on port %d.", CLIENT_PORT);
}
catch (Exception e)
{
writeln("Error while attempting to bind socket:");
writeln(e);
writeln("Replies will not be visible. Use a packet capture tool to see replies,\nor try re-running the program with more permissions.");
}
(new Thread(&listenThread)).start();
writeln("Type \"d\" to broadcast a DHCP discover packet.");
while (true)
{
auto line = readln().strip().split();
if (!line.length)
{
writeln("Enter a command.");
continue;
}
switch (line[0].toLower())
{
case "d":
case "discover":
sendPacket();
break;
default:
writeln("Unrecognized command.");
}
}
}
From my understanding I will have to send a BOOTP vendor extension if I want to query for a specific option. I am not a networking guru and am looking for as much help as possible thank you.
DHCP OPTIONS DOCUMENTATION:
http://www.networksorcery.com/enp/protocol/bootp/options.htm
If you only need a one-off experiment, you can modify the sendPacket function to include the DHCP option you need. Notice the line:
packet.options ~= DHCPOption(DHCPOptionType.dhcpMessageType, [DHCPMessageType.discover]);
You can copy and edit it to add more options to the DHCP packet.
As for decoding, the program will already print all the data it receives, but it may not know how to decode every option. You may need to add more options to the respective enumerations, and to the switch statement that controls how these options are parsed and printed.

Resources