IPv6 Programming on Windows Tutorial Part 6

IPv6 Programming on Windows Tutorial Part 6

tags:
Junya Kato
NTT Information Sharing Platform Laboratories



Introduction

Through the past five articles of this series, you have learned how to create a basic program using TCP.  This time, I will introduce an API, which I did not cover in previous articles, that will allow you to obtain detailed information about the network adapter (interface).  One example of adapter information would be the IPv6 address assigned to it.  Also, depending on the program you create, your program will probably need to obtain information about the kind of adapters that are installed and about which IPv6 address is assigned to each adapter.  The API that I will introduce this time will enable you to obtain detailed information about the adapter.  This detailed information includes not only the IPv6 address, but also other information such as the IPv4 address, MAC address, MTU value, and the adapter’s up/down status.


IP Helper API

Outline

The IP Helper API is a set of utility APIs that contains functions that assist the WinSock API.  The adapter information that I mentioned above cannot be obtained using only WinSock functions.  This package also provides routing table handling, DNS query functions, and ICMPv4/ICMPv6 functions that are normally handled using the RAW socket.  The function that I will explain is one of the functions for obtaining adapter information, the “GetAdaptersAddresses()” function.

Header and library for IP Helper API

As you can see below, you include the header file “iphlpapi.h” in the source code of the program that uses the IP Helper API.

#include ‹winsock2.h›
#include ‹ws2tcpip.h›
#include ‹iphlpapi.h› ←-- Header file necessary for the IP Helper API
        .
        .
    


Also, when linking the library, link the import library “iphlpapi.lib”.  Since it is separate from the WinSock2 API, you don’t need to link ws2_32.lib.  However, since both APIs are used together most of the time, they usually are both linked in at the same time.

C:\>cl v6programm.c ws2_32.lib iphlpapi.lib ←-- Import library for the IP Helper API


The GetAdaptersAddresses() function, which obtains the detailed adapter information

Outline of the GetAdaptersAddresses() function

First, I will show the function prototype for GetAdaptersAddresses() below.

DWORD WINAPI GetAdaptersAddresses(
  ULONG Family,
  DWORD Flags,
  PVOID Reserved,
  PIP_ADAPTER_ADDRESSES pAdapterAddresses,
  PULONG pOutBufLen
);


You can specify the address family by using the first argument, Family.  When obtaining address information, you can specify the following: AF_INET (obtains IPv4 only), AF_INET6 (obtains IPv6 only) or AF_UNSPEC (obtains both IPv4 and IPv6).  The second argument, Flags, also determines the kind of information to be obtained; it is possible to specify a combination of the values shown in Table 1.  I will explain the details of the content of flags when I talk about the fourth argument.

Table 1: Parameters that can be specified in the Flags variable

GAA_FLAG_INCLUDE_PREFIX Include IPv6 prefix in the obtained information
GAA_FLAG_SKIP_UNICAST Do not return unicast address
GAA_FLAG_SKIP_ANYCAST Do not return anycast address
GAA_FLAG_SKIP_FRIENDLY_NAME Do not return adapter’s friendly name (it is an adapter name like “Local Area Connection”)
GAA_FLAG_SKIP_MULTICAST Do not return multicast address
GAA_FLAG_SKIP_DNS_SERVER Do not return DNS server address

For the third argument, Reserved, always specify NULL.  The fifth argument is the buffer size needed to store adapter information.  Depending on the structure of the adapter and address information, the buffer space needed varies considerably.  Because of that, it is hard for a programmer to guess how much buffer space is needed in advance.  So, if you specify NULL in the fourth argument and call the GetAdaptersAddresses() function, it will store the buffer size needed to store the information in the pOutBufLen variable.  The function assumes that you will allocate a buffer of that size, then call the GetAdaptersAddresses() function again, specifying the buffer you allocated, where it will store the adapter information, as the fourth argument.

Since the pAdapterAddresses data structure is an IP_ADAPTER_ADDRESSES structure, we will look at its members in detail in the next section.

Also, the return values of the GetAdaptersAddresses() function are shown in table 2.

Table 2: GetAdaptersAddresses() function return values
ERROR_SUCCESS The call was successful
ERROR_BUFFER_OVERFLOW There was not enough space to store the adapter information.  In this case, the amount of memory needed will be stored in pOutBufLen.  Call the GetAdaptersAddresses() function again after allocating enough memory to obtain the information.
ERROR_INVALID_PARAMETER Cannot store the information in pAdapterAddresses.  An invalid pointer, such as for pOutBufLen, was specified.
ERROR_NOT_ENOUGH_MEMORY There was not enough memory to run the GetAdaptersAddresses() function.  Unlike ERROR_BUFFER_OVERFLOW, this is a critical error.
Values other than ERROR_SUCCESS Please obtain the error type by using the FormatMessage() function.

IP_ADAPTER_ADDRESSES structure

The IP_ADAPTER_ADDRESSES structure is defined in the header file “iptypes.h”.  When the header file “iphlpapi.h” is included, this header is also automatically included, so the programmer does not have to explicitly include it.

typedef struct _IP_ADAPTER_ADDRESSES {
    union {
        ULONGLONG Alignment;
        struct {
            ULONG Length;
            DWORD IfIndex;
        };
    };
    struct _IP_ADAPTER_ADDRESSES* Next;
    PCHAR AdapterName;
    PIP_ADAPTER_UNICAST_ADDRESS FirstUnicastAddress;
    PIP_ADAPTER_ANYCAST_ADDRESS FirstAnycastAddress;
    PIP_ADAPTER_MULTICAST_ADDRESS FirstMulticastAddress;
    PIP_ADAPTER_DNS_SERVER_ADDRESS FirstDnsServerAddress;
    PWCHAR DnsSuffix;
    PWCHAR Description;
    PWCHAR FriendlyName;
    BYTE PhysicalAddress[MAX_ADAPTER_ADDRESS_LENGTH];
    DWORD PhysicalAddressLength;
    DWORD Flags;
    DWORD Mtu;
    DWORD IfType;
    IF_OPER_STATUS OperStatus;
    DWORD Ipv6IfIndex;
    DWORD ZoneIndices[16];
    PIP_ADAPTER_PREFIX FirstPrefix;
} IP_ADAPTER_ADDRESSES, *PIP_ADAPTER_ADDRESSES;


One IP_ADAPTER_ADDRESSES instance is created per adapter.  When multiple adapters are installed in the host that calls the GetAdaptersAddresses() function, a linked list of IP_ADAPTER_ADDRESSES structures is created and the next adapter will be pointed to by the Next member.  In the last adapter, the Next member is NULL.  Now, we will take a look at the contents of the adapter information one by one.

The AdapterName member, which stores the adapter name

    PCHAR AdapterName;


A character string uniquely identifying the adapter is stored in the AdapterName variable.  However, it will be shown in a UID format just like the one shown below.  In the registry, the adapter information is stored in this format.

    "{F1CA1BF8-B3BF-1FEE-99DB-FEA9C9B57B9CC}"


The human-readable name seen in the GUI, such as “Local Area Connection” and “wireless network” will be stored in the FriendlyName member.

The FriendlyName member, which stores the human-readable adapter name, and the Description member, which stores the description

    PWCHAR FriendlyName;
    PWCHAR Description;


The human-readable adapter name is stored in FriendlyName, and the description is stored in Description.  The former is the network icon name that is displayed when Network Properties is opened; the latter is the description displayed when the Adapter Properties and such are opened.  The below shows an example of what is stored in these members.

  FriendlyName: “Local Area Connection”
  Description: "Intel(R) PRO/1000 MT Mobile Connection – Packet Scheduler Miniport


What you need to pay attention to is that these members are stored encoded in wide character (Unicode) format since they are defined as PWCHAR variables.  You need to convert the character coding when necessary.  In the sample code, they are displayed after being converted from Unicode to Shift JIS using the WideByteToMultiByte() function.

Note that when GAA_FLAG_SKIP_FRIENDLY_NAME is specified in the second argument of the GetAdaptersAddresses() function, Flags, this member is not returned.

FirstUnicastAddress member, which stores the list of unicast addresses

    PIP_ADAPTER_UNICAST_ADDRESS FirstUnicastAddress;


This list stores the unicast addresses given to the adapter.  Each instance of IP_ADAPTER_UNICAST_ADDRESS stores information about one unicast address.  When the adapter has multiple unicast addresses, a linked list of IP_ADAPTER_UNICAST_ADDRESS structures is created.  When the first argument of the GetAdaptersAddresses() function specifies the address family, only the specified addresses are listed.  Also, when GAA_FLAG_SKIP_UNICAST is specified in the second argument of the GetAdaptersAddresses() function, Flags, no information is stored in this member.

Definition of the IP_ADAPTER_UNICAST_ADDRESS structure

The IP_ADAPTER_UNICAST_ADDRESS structure is defined as follows:

typedef struct _IP_ADAPTER_UNICAST_ADDRESS {
    union {
        struct {
            ULONG Length;
            DWORD Flags;
        };
    };
    struct _IP_ADAPTER_UNICAST_ADDRESS* Next;
    SOCKET_ADDRESS Address;
    IP_PREFIX_ORIGIN PrefixOrigin;
    IP_SUFFIX_ORIGIN SuffixOrigin;
    IP_DAD_STATE DadState;
    ULONG ValidLifetime;
    ULONG PreferredLifetime;
    ULONG LeaseLifetime;
} IP_ADAPTER_UNICAST_ADDRESS, *PIP_ADAPTER_UNICAST_ADDRESS;


As with the IP_ADAPTER_ADDRESSES structure, the Next member is used to create a linked list that points to each of the unicast addresses and terminates the list with NULL.

The Address member, which stores the IP address

The Address member is a SOCKET_ADDRESS structure, and the IP address of the socket address can be obtained via the lpSockaddr variable.  Along with the socket address, the iSockaddrLength variable can be used to obtain the size of the socket address.

typedef struct _SOCKET_ADDRESS {
    LPSOCKADDR lpSockaddr;
    INT iSockaddrLength;
} SOCKET_ADDRESS, *PSOCKET_ADDRESS;


The SOCKET_ADDRESS structure is also unique to the WinSock API; the path to get to the actual socket address (lpSockaddr variable) is rather lengthy.

The PrefixOrigin and SuffixOrigin members, which are related to address types

The PrefixOrigin and SuffixOrigin variables show the origins of the prefix (network) part and host part of the address.  It can determine how the address was created; it can determine if it was created using DHCP, if it was configured using router advertisement, if it is an anonymous address, if it was setup manually or not, etc.  In the header file “iptypes.h”, the IP_PREFIX_ORIGIN and IP_SUFFIX_ORIGIN types are defined by the enum as shown below.

typedef enum {
    IpPrefixOriginOther = 0,
    IpPrefixOriginManual,
    IpPrefixOriginWellKnown,
    IpPrefixOriginDhcp,
    IpPrefixOriginRouterAdvertisement,
} IP_PREFIX_ORIGIN;

typedef enum {
    IpSuffixOriginOther = 0,
    IpSuffixOriginManual,
    IpSuffixOriginWellKnown,
    IpSuffixOriginDhcp,
    IpSuffixOriginLinkLayerAddress,
    IpSuffixOriginRandom,
} IP_SUFFIX_ORIGIN;  


The potential combinations of prefixes and suffixes are shown below, sorted by address protocol family and type:

  • For IPv4 addresses
    • When configured manually
      PrefixOrigin = IpPrefixOriginManual
      SuffixOrigin = IpSuffixOriginManual
    • When configured by DHCP
      PrefixOrigin = IpPrefixOriginDhcp
      SuffixOrigin = IpSuffixOriginDhcp
      For IPv6 addresses, prefix and suffix are the same type.
    • When dynamically assigned by a dial-up connection (PPP)
      PrefixOrigin = IpPrefixOriginManual
      SuffixOrigin = IpSuffixOriginManual
    • Loopback address (127.0.0.1)
      PrefixOrigin = IpPrefixManual
      SuffixOrigin = IpSuffixManual
  • For IPv6 addresses
    • When configured manually
      PrefixOrigin = IpPrefixOriginManual
      SuffixOrigin = IpSuffixOriginManual
    • Address whose host part was generated from the MAC address using EUI-64 after receiving RA
      PrefixOrigin = IpPrefixOriginRouterAdvertisement
      SuffixOrigin = IpSuffixOriginLinkLayerAddress
    • Anonymous address whose host part was generated randomly after receiving RA
      PrefixOrigin = IpPrefixOriginRouterAdvertisement
      SuffixOrigin = IpSuffixOriginRandom
    • Link-local address whose host part was generated from the MAC address using EUI-64
      PrefixOrigin = IpPrefixWellKnown
      SuffixOrigin = IpSuffixOriginLinkLayerAddress
    • Loopback address (::1)
      PrefixOrigin = IpPrefixWellKnown
      SuffixOrigin = IpSuffixWellKnown

The ValidLifetime, PreferredLifetime and LeaseLifetime members, which show an address’ lifetime

For addresses that are set to expire at a certain time, the length of their life span is set in three ULONG members.  All values are in seconds (sec).

  • ULONG ValidLifetime
  • ULONG PreferredLifetime
  • ULONG LeaseLifetime
  • For IPv4 addresses
    When the address is obtained using DHCP, it seems that the same life span is stored for all three variables.
  • For IPv6 addresses
    The longest life span (ValidLifetime) and recommended life span (PreferredLifetime), which are determined by the protocol spec, are stored.  Currently it doesn’t look like LeaseLifetime is used; however, I think it may be a member that is used to store lease time when addresses are assigned in a stateful way such as DHCPv6.

DadState, which stores DAD (Duplicated Address Detection) status

This member stores the status of the functionality that detects duplicate IPv6 addresses.  As for IPv4 addresses, since there is no DAD functionality, DadState member values have no meaning.  In the header file “iptypes.h”, the enum defines five statuses.

typedef enum {
    IpDadStateInvalid    = 0,
    IpDadStateTentative,    // Temporary use
    IpDadStateDuplicate,    // Duplication detected
    IpDadStateDeprecated,   // Expired
    IpDadStatePreferred,    // Valid IPv6 address
} IP_DAD_STATE;


The FirstAnycastAddress and FirstMulticastAddress members, which store anycast and multicast address lists

IP_ADAPTER_ANYCAST_ADDRESS structure is the same as the FirstUnicastAddress member that stores unicast addresses in that it creates a list and stores multiple addresses.  However, since the life span for anycast and multicast addresses cannot be set on the protocol stack, members are significantly simplified and only address information is stored for these structures.

typedef struct _IP_ADAPTER_ANYCAST_ADDRESS {
    union {
        ULONGLONG Alignment;
        struct {
            ULONG Length;
            DWORD Flags;
        };
    };
    struct _IP_ADAPTER_ANYCAST_ADDRESS *Next;
    SOCKET_ADDRESS Address;
} IP_ADAPTER_ANYCAST_ADDRESS, *PIP_ADAPTER_ANYCAST_ADDRESS;

typedef struct _IP_ADAPTER_MULTICAST_ADDRESS {
    union {
        ULONGLONG Alignment;
        struct {
            ULONG Length;
            DWORD Flags;
        };
    };
    struct _IP_ADAPTER_MULTICAST_ADDRESS *Next;
    SOCKET_ADDRESS Address;
} IP_ADAPTER_MULTICAST_ADDRESS, *PIP_ADAPTER_MULTICAST_ADDRESS;


The FirstDnsServerAddress and DnsSuffix members, which store DNS information

DNS information includes the DNS server’s IP address and the domain name.  These are set in the FirstDnsServerAddress variable, which is an IP_ADAPTER_DNS_SERVER_ADDRESS structure, and the DnsSuffix variable (PWCHAR type).  As for the format of the former, when multiple addresses are stored, an IP_ADAPTER_DNS_SERVER_ADDRESS structure list is created, just like with anycast and multicast addresses.  The structure contents are also the same.

typedef struct _IP_ADAPTER_DNS_SERVER_ADDRESS {
    union {
        ULONGLONG Alignment;
        struct {
            ULONG Length;
            DWORD Reserved;
        };
    };
    struct _IP_ADAPTER_DNS_SERVER_ADDRESS *Next;
    SOCKET_ADDRESS Address;
} IP_ADAPTER_DNS_SERVER_ADDRESS, *PIP_ADAPTER_DNS_SERVER_ADDRESS;


Also, the latter, DnsSuffix, will store the domain name.  What you need to be careful about is the fact that it is a PWCHAR type variable and the character string is stored in wide character (Unicode) encoding.  Similar to the FriendlyName member, you may have to convert it to multibyte characters.

The IfType member, which shows the adapter type

This variable shows the adapter’s media type.  Types include Ethernet, Tunnel Adapter, Dial-up Adapter, Loopback Adapter, etc.  The specifics are defined in the header file “ipifcons.h” and each starts with IF_TYPE*.  The primary defines are the numbers shown below.

#define IF_TYPE_ETHERNET_CSMACD         6
#define IF_TYPE_PPP                     23
#define IF_TYPE_SOFTWARE_LOOPBACK       24
#define IF_TYPE_TUNNEL                  131 // Encapsulation interface


The PhysicalAddress and PhysicalAddressLength members, which store the adapter’s physical address

BYTE PhysicalAddress[MAX_ADAPTER_ADDRESS_LENGTH];
DWORD PhysicalAddressLength;


These members store the adapter’s physical address.  For example, a 6-byte MAC address is given to the Ethernet adapter.  Since it is not always 6-byte for other media, MAX_ADAPTER_ADDRESS_LENGTH (8 bytes), the maximum size, is allocated, and the actual length of the physical address is stored in the PhysicalAddressLength variable.  A pseudo-physical address is created for adapters that were created like software, e.g. Tunnel Adapter; as for tunnel type virtual adapters such as the “6to4 Pseudo-Interface” and “Teredo Tunneling Pseudo-Interface”, they are 4 bytes in length.  Note that the loopback adapter does not have a physical address.  In this case, PhysicalAddressLength is 0.

The Mtu member, which stores the adapter’s MTU value

This member stores the MTU value for the adapter.  For Ethernet, the Mtu variable value is typically 1500.  If the MTU value is changed by the user, that value is stored.

The OperStatus member, which stores the adapter’s status

The OperStatus member is an IF_OPER_STATUS variable, and it shows the adapter’s status such as up/down.  The IF_OPER_STATUS type variables are defined by the enum shown below.

typedef enum {
    IfOperStatusUp = 1,         // Adapter is up
    IfOperStatusDown,           // Adapter is down
    IfOperStatusTesting,        // Adapter is currently checking the link
    IfOperStatusUnknown,        // Adapter’s status is unknown
    IfOperStatusDormant,        // Adapter is dormant
    IfOperStatusNotPresent,     // Adapter does not exist
    IfOperStatusLowerLayerDown  // Link Layer and below that are down
} IF_OPER_STATUS;


The Ipv6IfIndex member, which shows the adapter (interface) index

The Ipv6IfIndex member is valid only for IPv6.  It is the number that shows adapter (interface) index.  For example, when describing the link local address, the adapter (interface) is specified using “%”, as shown below.

  fe80::1%6

The number specified here matches the value of Ipv6ifIndex.

The Flags member, which shows the adapter’s attributes

This member stores the target adapter’s attributes.  The possible values for the Flags member are any combination of the values shown in Table 3.

Table 3: The adapter attributes returned in the Flags member
IP_ADAPTER_DDNS_ENABLED Registers the host name to DNS dynamically (DynamicDNS is enabled)
IP_ADAPTER_REGISTER_ADAPTER_SUFFIX Adds DNS suffix (domain name).  (please refer to the detailed setup for TCP/IP properties)
IP_ADAPTER_DHCP_ENABLED Indicates that the address is obtained by DHCP.  On Windows XP SP2, this flag is only set for IPv4 addresses.  If DHCPv6 is implemented in Windows Vista in the future, it is possible that this flag may be set even for IPv6 addresses; however, it is also possible that a new flag will be created to distinguish between DHCPv4 and DHCPv6.
IP_ADAPTER_RECEIVE_ONLY Receive only adapter.
IP_ADAPTER_NO_MULTICAST Adapter does not support multicast
IP_ADAPTER_IPV6_OTHER_STATEFUL_CONFIG Adapter carries out other stateful configuration (see end note 1).  It is not implemented in Windows XP SP2.

The ZoneIndices member, which stores zone information

DWORD ZoneIndices[16];


The ZoneIndices member consists of a 16-element DWORD array.  Specify the SCOPE_LEVEL enum values that describes the zone in the index part of the array.  A variable with SCOPE_LEVEL type can be set to one of the values shown below.

typedef enum 
{
    ScopeLevelInterface = 1,
    ScopeLevelLink = 2,
    ScopeLevelSubnet = 3,
    ScopeLevelAdmin = 4,
    ScopeLevelSite = 5, 
    ScopeLevelOrganization = 8, 
    ScopeLevelGlobal = 14
} SCOPE_LEVEL;


For example, when the applicable interface uses a site local address, if you reference the ZoneIndices[] array using ScopeLevelSite as the index, you can obtain the integer value that will show the zone index.

  ZoneIndices[ScopeLevelSite]


So far as looking at “netsh interface ipv6show inteface” or “ipv6 if” command output (Figure 1), it seems that only link scope (scope of the link local address), site scope (scope of the site local address (see end note 2) ), and global scope (scope of the global address) are used.

Figure 1: Example of netsh interface ipv6 show interface output for “Local Area Connection”

Connection name : local area connection
GUID : {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}
Status : Disconnected
.  
.  
Firewall : disabled
Length of the site prefix : 48 bits
Zone ID - Link : 6 ← scope of the link local address
Zone ID - Site : 1 ← scope of the site local address


Since the scope of the link local address depends on the adapter, the zone number (ZoneIndices[ScopeLevelLink]) matches the value of the Ipv6IfIndex member, which is the adapter’s (interface) index.  The site local address zone number (ZoneIndices[ScopeLevelSite]) matches the zone number that the user specified by using “netsh interfaceipv6 set interface ‘adapter name’ siteid=‘zone number’ ” command.  The global address zone number (ZoneIndices[ScopeLevelGlobal]) is the same value for all the adapters.

The FirstPrefix member, which stores the adapter’s prefix

This member stores the adapter’s prefix information.  In order to obtain this member information, you need to specify GAA_FLAG_INCLUDE_PREFIX in the second argument, Flags, in advance when calling the GetAdaptersAddresses() function.  If this flag is not specified, the FirstPrefix member is set to NULL.  The information stored in the FirstPrefix member is the adapter’s prefix (the last 64 bits are zeroed out).  The difference from the FirstUnicastAddress member is that the FirstUnicastAddress member stores unicast addresses.  For example, when the following two IPv6 unicast addresses have the same adapter prefix,

  fe80::1
  fe80::2012:34ff:fe56:789a

FirstUnicastAddress creates a list of two addresses; however, the FirstPrefix member creates only one element with the prefix fe80::/64.  For IPv4 addresses, a network address that has the host part zeroed out can be also obtained; however, the member for obtaining the netmask is missing.  Note that if you use the old GetAdapterInfo()API (see end note 3), it is possible to obtain complete information on IPv4 addresses.

[Continues in Part 2]

End Notes

1.  “other stateful configuration” is a mechanism that creates an IPv6 address based on the prefix information option included in the RA and obtains other information such as DNS in a stateful way, e.g. DHCPv6, when an RA that contains a specific flag called the O flag is received.  The definition of the O flag embedded in RA and how to use it are provided in RFC2461 and draft-ietf-ipv6-ra-mo-flags-01.txt.

2.  It has been already decided that the use of site local addresses will be discontinued, so you should not start using it now (RFC3879).  On Windows XP SP2, the setup items were left in since it is still implemented.

3.  The GetAdaptersInfo() function is also an API for obtaining adapter information.  However, it obtains IPv4 address information only.  That is why the GetAdaptersAddresses() function, which supports IPv6, was created.




この記事のトラックバックURL

http://www.ipv6style.jp/trackback/632
Ads by Google