IPSCOPE

[Settings]
IPSCOPE_0=string-expression  ; Default: -
…
IPSCOPE_9=string-expression

IPSCOPE_n defines based on the client request if this scope matches or not.

Mulitple Scopes (available since V2.3)

What are scopes?

Scopes are a way to define what information is passed from the DHCP server to the client. Not all clients are the same. There may be mobile devices, test equipment, IP phones, guest laptops, you name it. Sometimes it is necessary to use in example a different range of IP address depending on what type of device it is. This can be achieved with the DHCP Server for Windows with the following INI file:

[Settings]
AssociateBindsToPools=1 
Trace=1
IPBIND_0=192.168.5.1
IPPOOL_0=192.168.5.2-49
IPSCOPE_0=if(vendorclass=="PHONE-VENDOR","Phones", null) ; 

IPBIND_1=192.168.5.1
IPPOOL_1=192.168.5.50-99

[General]
SUBNETMASK=255.255.255.0

With that INI file all devices with a vendorclass “PHONE-VENDOR” get an IP address from the range 192.168.5.2 to 192.168.5.49. All other devices get an IP address out of the range from 192.168.5.50 to 192.168.5.99.

The DHCP server checks all scopes starting with IPSCOPE_0 to IPSCOPE_9 if it matches to the client request. The first match is picked and the corresponding IPPOOL_n is used as the IP address range. The key to all this is obviously the IPSCOPE_n setting which was introduced with V2.3 of the DHCP server.

IPSCOPE_n syntax

The definition of IPSCOPE_n is like it’s own little programming language. The result of the expression formulated in that language is a string. If that string results to a null value, then that particular scope does not match to the client request (e.g. wrong vendor class). If the resulting string is not null then we have a match.

The following is the definition of the IPSCOPE_n syntax:

string_expression ::= simple_string_expression
     | 'if' '(' logical_expression ',' string_expression ',' string_expression ')'
     | 'concat' '(' string_expression ( ',' string_expression )* ')' 
     | 'substring' '(' string_expression ',' start_offset ',' length ')' 
     | 'firstvalue' '(' string_expression ( ',' string_expression )* ')'
     | 'inistring' '(' string_expression ')' 

string_expression

if With if(expr, then, else) you can define the resulting string based on the logical-expression result. If the logical-expression is true then the first string-expression (then) is returned, otherwise the second string-expression (else) is returned.
concat The result of concat(string1, …, stringN) is the concatanation of all strings. Example: concat(“prefix_”, “string”) results to “prefix_string”.
substring The result of substring(string, start, len) is a sub-string of len characters starting with the character at offset start (0 based). Example: substring(“hello tom”, 6, 3) is “tom”. If the input string does not contain enough characters, then the result is cut accordingly. The result of substring(“hello tom”, 6, 10) is still “tom”.
firstvalue firstvalue is actually a convinience function. It shortens if/then/else chains such as:
if (vendorclass!=null, vendorclass, if(userclass!=null, userclass, null))
This returns vendorclass or userclass if it is defined (not null) and null if both are not defined. This can be expressed very easy with firstvalue:
firstvalue(vendorclass, userclass, null)
firstvalue basically results in the first parameter that is not null.
inistring inistring allows you to evaluate an INI string in the client section of the client that asks for an IP address. Say you want to distinguish clients that have a dynamically assigned IP address from statically allocated clients. Then the following IPSCOPE_0 definition allows you to do so:[Settings]
IPSCOPE_0 = if (!isKnownClient || inistring(“AutoConfig”) != “”, “DYNAMIC”, “STATIC”) ;This IPSCOPE_0 line results to DYNAMIC, if client is unknown or AutoConfig is defined. It results to STATIC, otherwise.
simple_string_expression ::= "simple string in quotes"
     | 'vendorclass'
     | 'userclass'
     | 'hostname'
     | 'chaddr'
     | 'ciaddr'
     | 'clientid'
     | 'null'
     | 'OPTION_nnn'
     | 'OPTIONHEX_nnn'
simple_string_expression
vendorclass Returns the dhcp option 60 string from the current dhcp request.
userclass Returns the dhcp option 77 string from the current dhcp request.
hostname Returns the dhcp option 12 string from the current dhcp request.
chaddr Returns the mac address as it is supplied in the chaddr field of the current dhcp request header as a string. Format of string is “00:01:02:03:04:05″. Including the colons (:) without the quotes (“). Hexadecimal values A-F are encoded in uppercase letters, e.g. “00:1A:2B:3C:4D:5E”.
ciaddr Returns the ip address as it is supplied in the ciaddr field of the current dhcp request header as a string. Format of string is “123.456.78.9”. Including the dots (.) without the quotes (“).
clientid Returns the option 61 client id value as it is supplied in the options of the current dhcp request as a string. Format of string is either regular ascii text in case of type 0 encoding or as a colon separated mac address as in chaddr. The encoding type field is not part of the resulting string. If no client id is given as option 61 then the mac address is returned instead. (Supported since V2.5.0).
OPTION_nnn Returns the content of the dhcp option nnn as a string. No conversion of any kind is performed. nnn is a decimal number with no leading 0’s. Example: substring(option_61,1,9) results into a string of (max) 9 characters starting at offset 1 of the dhcp option 61. Option 61 is the client identifier. Client identifier uses the first character as a tag value, therefore the string starts at offset 1.
OPTIONHEX_nnn Returns the content of the dhcp option nnn as a hexadecimal string dump. nnn is a decimal number with no leading 0’s. Example: optionhex_61 results into a string of 7 hex values of the following form 01:00:1A:22:BB:03:66 representing the option 61 client identifier constisting of 7 octets. The first 01 is the encoding type and the remaining 6 octets are the MAC address of the client. (Supported since V2.5.0).
logical_expression ::= bool_expression (('||'|'&&') logical_expression)?
logical_expression
expr1 || expr2 The result is false, if both expressions are false. It’s true otherwise.
expr1 && expr2 The result is true, if both expressions are true. It’s false otherwise.
bool_expression ::= simple_bool_expression
     | '!' simple_bool_expression
     | string_expression '==' string_expression
     | string_expression '!=' string_expression
     | string_expression '~=' string_expression
     | string_expression '~~' string_expression
bool_expression
string1 == string2 The result is true, if both strings are identical. The comparison is case sensitive.
string1 != string2 The result is true, if both strings are not identical. The comparison is case sensitive.
string ~= templ The result is true, if the string matches the template. The template can include DOS-like wildcard characters (?, *). The comparison is case sensitive.
string ~~ templ The result is true, if the string matches the template. The template can include DOS-like wildcard characters (?, *). The comparison is case insensitive. Example:
if (substring(option_61,1,30) ~~ “PXE*”, “pxe-client”, null).
The result of this is “pxe-client”, if the client identifier starts with “PXE”, otherwise null.
simple_bool_expression ::= 'true'
     | 'false'
     | '(' logical_expression ')'
     | 'isKnownClient'
simple_bool_expression
isKnownClient The result is true, if the client requesting an IP address already has a client section in the INI file. If it is a new client that is not part of the INI file yet, then the result is false.

The complete language is case insensitive. So all examples and definitions above work lower case as well as in upper case letters.
The syntax diagrams (aka rail road diagrams) are generated with: Railroad Diagram Generator.

Scope sections

The result of the IPSCOPE_n string-expression is a string. This string determines whether or not this particular scope matches to the client request or not. If the string-expression results to a null string, then the scope does not match. If the string is not null, then the scope does not only match, the resulting string also defines the scope section.

The order in which the DHCP result options are compiled into the DHCP response is:

  1. Client section [00-01-02-03-04-05] or [client-id]
  2. Scope section [result of IPSCOPE_n]
  3. [General_n] section
  4. [General] section

Here is an example:

[Settings]
AssociateBindsToPools=1 
Trace=1
IPBIND_0=192.168.5.1
IPPOOL_0=192.168.5.2-49
IPSCOPE_0=if(vendorclass=="PHONE-VENDOR","Phones", null) ; 

IPBIND_1=192.168.5.1
IPPOOL_1=192.168.5.50-99

[General]
SUBNETMASK=255.255.255.0

[Phones]
OPTION_66=192.168.5.1 ;

IPSCOPE_0 is null for everything but phones from vendor “PHONE-VENDOR”. Phones have a scope section called “Phones”. All other devices do not get the options returned that are defined in the [Phones] scope section.