PwSh.Fw.Network.DHCPv4.psm1

<#
.SYNOPSIS
Module to handle DHCP requests
 
.DESCRIPTION
Module to handle DHCP packets
 
.NOTES
 
#>


# DHCP Packet Format (RFC 2131 - http://www.ietf.org/rfc/rfc2131.txt):
# 0 1 2 3
# 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
# | op (1) | htype (1) | hlen (1) | hops (1) |
# +---------------+---------------+---------------+---------------+
# | xid (4) |
# +-------------------------------+-------------------------------+
# | secs (2) | flags (2) |
# +-------------------------------+-------------------------------+
# | ciaddr (4) |
# +---------------------------------------------------------------+
# | yiaddr (4) |
# +---------------------------------------------------------------+
# | siaddr (4) |
# +---------------------------------------------------------------+
# | giaddr (4) |
# +---------------------------------------------------------------+
# | |
# | chaddr (16) |
# | |
# | |
# +---------------------------------------------------------------+
# | |
# | sname (64) |
# +---------------------------------------------------------------+
# | |
# | file (128) |
# +---------------------------------------------------------------+
# | |
# | options (variable) |
# +---------------------------------------------------------------+

$Script:socketIn = $null
$Script:socketOut = $null

<#
.SYNOPSIS
Convert bytes to a human-readable string
 
.DESCRIPTION
Convert an array of bytes to a human-readable string.
 
.PARAMETER bytes
Array of bytes
 
.EXAMPLE
$a = new-object system.text.asciiEncoding
$bytes = $a.GetBytes("This is a string")
Convert-BytesToString -bytes $bytes
 
Should return "This is a string"
 
.NOTES
General notes
#>

function Convert-BytesToString {
    [CmdletBinding()]
    [OutputType([String])]
    Param (
        [Parameter(Mandatory = $true, ValueFromPipeLine = $true)][byte[]]$bytes
    )
    Begin {
        Write-EnterFunction
        $char = @()
    }

    Process {
        $char += -join ($bytes | ForEach-Object { [char]$_ })
    }

    End {
        return -join $char
        Write-LeaveFunction
    }
}

<#
.SYNOPSIS
Convert bytes to an hexadecimal string
 
.DESCRIPTION
Convert an array of bytes to an hexadecimal string.
 
.PARAMETER bytes
Array of bytes
 
.EXAMPLE
$a = new-object system.text.asciiEncoding
$bytes = $a.GetBytes("This is a string")
Convert-BytesToHex -bytes $bytes
 
Should return "This is a string"
 
.NOTES
General notes
#>

Function Convert-BytesToHex {
    [CmdletBinding()]
    [OutputType([String])]
    param(
        [parameter(Mandatory = $true, ValueFromPipeLine = $true)][Byte[]]$Bytes
    )
    Begin {
        Write-EnterFunction
        $char = @()
    }

    Process {
        $HexString = [System.Text.StringBuilder]::new($Bytes.Length * 2)
        ForEach($byte in $Bytes){
            $HexString.AppendFormat("{0:x2}", $byte) | Out-Null
        }
        $char += $HexString.ToString()
    }

    End {
        return -join $char
        Write-LeaveFunction
    }
}

<#
.SYNOPSIS
Convert bytes to int
 
.DESCRIPTION
Convert an array of bytes to an int
 
.PARAMETER bytes
Array of bytes
 
.EXAMPLE
[byte]$byte = "0xFF"
Convert-BytesToHex -bytes $byte
 
Should be 255
 
.NOTES
General notes
#>

function Convert-BytesToInt {
    [CmdletBinding()]
    [OutputType([int])]
    Param (
        [Parameter(Mandatory = $true, ValueFromPipeLine = $true)][byte[]]$bytes
    )
    Begin {
        Write-EnterFunction
        $b = @()
    }

    Process {
        $b += $bytes
    }

    End {
        return [int]"0x$(Convert-BytesToHex -Bytes $b)"
        Write-LeaveFunction
    }
}


<#
.SYNOPSIS
Short description
 
.DESCRIPTION
Long description
 
.PARAMETER string
Parameter description
 
.EXAMPLE
An example
 
.NOTES
General notes
#>

function Send-DHCPv4Packet {
    [CmdletBinding()]Param (
        [Parameter(Mandatory = $true, ValueFromPipeLine = $true)][DHCPv4Packet]$Packet,
        [Alias('Destination')]
        [Parameter(Mandatory = $false, ValueFromPipeLine = $false)][ipAddress]$SendTo =[net.ipAddress]::Broadcast,
        [Parameter(Mandatory = $false, ValueFromPipeLine = $false)][uint16]$port = 67
    )
    Begin {
        Write-EnterFunction
    }

    Process {
        $bytesSent = 0
        $bytes = $Packet.ToBytes()
        $client = new-object net.sockets.udpClient
        $client.Connect($SendTo, $port)
        try {
            # $bytesSent = $client.send($bytes, $bytes.length, $ip, 67)
            $bytesSent = $client.send($bytes, $bytes.length)
        } catch {
            Write-Error "$_"
        } finally {
            $client.close()
        }
        Write-Debug "$bytesSent bytes sent."
    }

    End {
        Write-LeaveFunction
    }
}

function Receive-DHCPv4Packet {
    [CmdletBinding()]
    [OutputType([DHCPv4Packet])]
    Param (
        [Parameter(Mandatory = $false, ValueFromPipeLine = $false)][string]$xid,
        [Alias('ipAddress')]
        [Parameter(Mandatory = $false, ValueFromPipeLine = $false)][ipAddress]$RecvFrom = [net.ipAddress]::any,
        [Parameter(Mandatory = $false, ValueFromPipeLine = $false)][uint16]$port = 67,
        [uint16]$timeout = 1000,
        [uint16]$retries = 10
    )
    Begin {
        Write-EnterFunction
    }

    Process {
        # $packet = [DHCPv4Packet]::New()
        # $packet.Receive($ipaddress, $xid)
        $client = new-object net.sockets.udpClient($port)
        # $client = new-object net.sockets.udpclient
        # $client.Connect($RecvFrom, $port)
        $client.Client.ReceiveTimeout = $timeout
        $ipEP = new-object net.ipendpoint($RecvFrom, $port)
        try {
            $i = 0
            while ($true) {
                try {
                    [byte[]]$bytesReceived = $client.receive([ref]$ipEP)
                    Write-Debug "$($bytesReceived.count) bytes received."
                    # [DHCPv4Packet]$response = [DHCPv4Packet]::New($bytesReceived)
                    $Rxid = ($bytesReceived[4..7] | Convert-BytesToHex)
                    $Rchaddr = $bytesReceived[28..33].ForEach('ToString', 'x2') -join ":"
                    Write-Devel "${i}: $Rxid : $($Rchaddr) from $($ipEP.Address.ToString()):$($ipEP.Port.ToString())"
                } catch {
                    Write-Devel "$_, retrying"
                } finally {
                    $i++
                }
                if ($i -gt $retries) { break }
            }
        } catch {
            Write-Error "$_"
        } finally {
            $client.close()
        }

        return $receive
    }

    End {
        Write-LeaveFunction
    }
}