43
Tags:
c++
Skrevet af
Bruger #2695
@ 16.05.2004
Introduktion
Hej igen. Så fandt jeg alligevel et lille emne, som jeg ikke har dækket i de tidligere artikler, og det er et finde ud af de lokale IP adresser.
Jeg har set mange kreative måder at finde IP adresser på, men de fleste brugte kald til eksterne programmer. Én måde et at kalde
ipconfig under Windows og
ifconfig under Linux og pipe dens output til en fil, som derefter åbnes og parses. Andre sender lige outputtet gennem
grep eller
awk for at filtrere alt skidtet fra. Alle disse metoder er lige dårlige, for hvis de eksterne programmer ikke er installeret, eller path ikke sat rigtigt op, så virker det bare ikke. Og de programmer kan jo få fat i adressen selv, så hvorfor skulle vi ikke også ?
Det kan vi også, og i denne artikel udbygger vi
IPAddress klassen, som vi udviklede i første artikel, så den kan skaffe os vores lokale adresser.
Når jeg siger lokale IP adresser, så mener jeg de adresser, som netkortene er tildelt. Ikke den adresse, som en web server på Internettet ser. De behøver nemlig ikke, at være de samme. Dine netkort har sikkert adresser i det lokale LAN, men når dine data pakker når til din ISP's router eller firewall, så kan adressen blive oversat. Dette kaldes NAT (Network Address Translation), og Google kan fortælle dig en hel masse om, hvordan det virker, hvis du er interesseret.
Derudover vil vi også have fat i maskinens host name.
En IP adresse lister
Vi vil lave et program, som skriver en liste over alle vores IP adresser og host name, og til det vil vi bruge følgende funktioner under Linux:
#include <sys/ioctl.h>
#include <unistd.h>
int gethostname(char *name, size_t len);
int ioctl(int d, int request, ...)
...og følgende under Windows:
int gethostname(char* name, int namelen);
int WSAIoctl(
SOCKET s,
DWORD dwIoControlCode,
LPVOID lpvInBuffer,
DWORD cbInBuffer,
LPVOID lpvOutBuffer,
DWORD cbOutBuffer,
LPDWORD lpcbBytesReturned,
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
typedef struct _SOCKET_ADDRESS_LIST {
INT iAddressCount;
SOCKET_ADDRESS Address[1];
} SOCKET_ADDRESS_LIST, FAR * LPSOCKET_ADDRESS_LIST;
typedef struct _SOCKET_ADDRESS {
LPSOCKADDR lpSockaddr;
INT iSockaddrLength;
} SOCKET_ADDRESS,*PSOCKET_ADDRESS;
ioclt/
WSAIoctl funktionen bruges til at læse eller ændre en masse forskellige socket relaterede indstillinger i operativ systemet. Hvilken, det er, specificeres med den anden parameter. For at læse adresselisten bruger vi konstanten
SIOCGIFCONF under Linux og
SIO_ADDRESS_LIST_QUERY under Windows.
Den første parameter i begge versioner specificerer en socket, som man ønsker oplysninger om. Hvis man vil vide noget om Internet adresser, skal man skaffe listen over adresser, som Internet sockets kan binde til. Derfor skal man oprette en Internet socket først, som vi jo har set på før. De andre parametre specificerer buffere og bufferstørrelser, som informationerne skal lægges i.
Vi prøver et kort eksempel, som lister vores IP adresser.
#include <iostream>
#include "Net.h"
#include "Exception.h"
#if defined(__linux__)
#include <unistd.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#elif defined(_WIN32)
#include <winsock2.h>
#endif
using namespace std;
int main(int argc, char ** argv)
{
try
{
netInit();
char hostname[256];
if(gethostname(hostname,256) != 0)
{
cout << "Kunne ikke skaffe hostname." << endl;
netRelease();
return -1;
}
//Skriv host name
cout << hostname << endl;
#if defined(__linux__)
//Vi laver et array med plads til 10 adresse strukturer
//Det burde være nok
struct ifreq my_ifreqs[10];
struct ifconf my_ifconf;
my_ifconf.ifc_len = sizeof(my_ifreqs);
my_ifconf.ifc_req = my_ifreqs;
int num, i, sock = socket(AF_INET,SOCK_DGRAM,0);
if(socket == 0)
{
cout << "Kunne ikke oprette socket." << endl;
netRelease();
return -1;
}
//Spørg Linux om adresser
if(ioctl(sock,SIOCGIFCONF,&my_ifconf) < 0)
{
cout << "Fejl i ioctl()." << endl;
close(sock);
netRelease();
return -1;
}
//Frigiv vores socket
close(sock);
//Antallet af fundne adresser er lig med antallet af
//returnerede bytes (my_ifconf.ifc_len) divideret med
//størrelsen på en enkelt adressestruktur (sizeof(struct ifreq))
num = my_ifconf.ifc_len / sizeof(struct ifreq);
//Skriv alle adresser ud
for(i = 0; i < num; i++)
{
struct sockaddr_in * adr = (struct sockaddr_in *)&my_ifreqs[ i ].ifr_ifru.ifru_addr;
cout << inet_ntoa((struct in_addr)adr->sin_addr) << endl;
}
#elif defined(_WIN32)
int i,s;
//Opret buffer med plads til 10 adresse strukturer
char buffer[sizeof(INT) + (sizeof(SOCKET_ADDRESS) * 10)];
DWORD returnedBytes;
LPSOCKET_ADDRESS_LIST adrList = (LPSOCKET_ADDRESS_LIST)buffer;
adrList->iAddressCount = 10;
if((s = socket(AF_INET,SOCK_DGRAM,0)) == INVALID_SOCKET)
{
cout << "Kunne ikke oprette socket." << endl;
netRelease();
return -1;
}
//Spørg Windows om adresser
if(WSAIoctl(s, SIO_ADDRESS_LIST_QUERY, NULL, 0, buffer, sizeof(buffer), &returnedBytes,NULL, NULL) == SOCKET_ERROR)
{
cout << "Fejl i WSAIoctl()." << endl;
closesocket(s);
netRelease();
return -1;
}
//Frigiv vores socket
closesocket(s);
//Skriv alle adresser ud
for(i = 0; i < adrList->iAddressCount; i++)
{
if(adrList->Address[ i ].lpSockaddr->sa_family == AF_INET)
{
struct sockaddr_in * adrIn = (struct sockaddr_in *)adrList->Address[ i ].lpSockaddr;
cout << inet_ntoa(adrIn->sin_addr) << endl;
}
}
#endif
netRelease();
} catch (Exception & ex)
{
cout << "Kunne ikke initialisere net komponenten: " << ex << endl;
}
return 0;
}
Som det ses, er det ikke meget kode, der skal til, og så er det ikke en grim løsning, hvor der bruges eksterne programmer.
Vi prøver at køre det under Linux:
[robert@codemachine Socket5]$ /sbin/ifconfig|grep "inet addr"
inet addr:192.168.1.3 Bcast:192.168.1.255 Mask:255.255.255.0
inet addr:127.0.0.1 Mask:255.0.0.0
inet addr:192.168.57.1 Bcast:192.168.57.255 Mask:255.255.255.0
[robert@codemachine Socket5]$ ./test
codemachine.the-playground.dk
127.0.0.1
192.168.1.3
192.168.57.1
[robert@codemachine Socket5]$
...og under Windows:
E:\\Socket5>ipconfig
Windows 2000 IP Configuration
Ethernet adapter Local Area Connection:
Connection-specific DNS Suffix . :
IP Address. . . . . . . . . . . . : 192.168.1.5
Subnet Mask . . . . . . . . . . . : 255.255.255.0
Default Gateway . . . . . . . . . : 192.168.1.1
E:\\Socket5>net5
windows
192.168.1.5
E:\\Socket5>
Under Linux vises loopback adressen 127.0.0.1, men det gør Windows versionen ikke. Min Linux maskine har to netkort (et fysisk og et virtuelt), hvorimod min Windows maskine kun har et enkelt. Alle oplysninger, som operativsystemet har, bliver givet videre til vores program.
Men vi er selvfølgelig ikke tilfredse endnu, for det skal selvfølgelig pakkes ind i
IPAddress klassen.
Tilføjelsen til IPAddress
Vi vil tilføje en statisk metode til
IPAddress klassen, som returnerer en instans af
IPAddress klassen med vores lokale adresser. For at gøre det må vi også kunne oprette tomme adresser, og til det skal vi bruge en ny constructor. Den vil jeg placere i
private området, så vi undgår, at tomme adresser gives til vores socket klasser.
IPAddress.h skal altså udbygges med:
//IPAddress.h
//.....
class IPAddress
{
public:
//.....
//Returnerer et IPAddress objekt med informationer
//om den lokale maskine
static IPAddress getLocalHost();
//.....
private:
//.....
//Laver et tomt IPAddress objekt som kan udfyldes med informationer
//senere
IPAddress();
//.....
};
//.....
Implementeringen er næsten magen til vores test kode fra før:
//IPAddress.cpp
//.....
#if defined(__linux__)
extern int h_errno;
extern int errno;
#include <net/if.h>
#include <sys/ioctl.h>
#elif defined(_WIN32)
#include <windows.h>
#endif
//.....
IPAddress::IPAddress()
{
}
IPAddress IPAddress::getLocalHost()
{
//IPAddress objekt som vi vil returnere
IPAddress localHost;
//Til at indeholde host name
char hostname[256];
//Hent host name
if(gethostname(hostname,256) != 0)
{
throw NetException(strerror(errno));
}
//Sæt hostname
localHost.m_officialName = string(hostname);
#if defined(__linux__)
//Vi laver et array med plads til 10 adresse strukturer
//Det burde være nok
struct ifreq my_ifreqs[10];
struct ifconf my_ifconf;
my_ifconf.ifc_len = sizeof(my_ifreqs);
my_ifconf.ifc_req = my_ifreqs;
int num, i, sock = socket(AF_INET,SOCK_DGRAM,0);
if(socket == 0)
{
throw NetException(strerror(errno));
}
//Spørg Linux om adresser
if(ioctl(sock,SIOCGIFCONF,&my_ifconf) < 0)
{
close(sock);
throw NetException(strerror(errno));
}
//Frigiv vores socket
close(sock);
//Antallet af fundne adresser er lig med antallet af
//returnerede bytes (my_ifconf.ifc_len) divideret med
//størrelsen på en enkelt adressestruktur (sizeof(struct ifreq))
num = my_ifconf.ifc_len / sizeof(struct ifreq);
//Læg alle adresser i localHost
for(i = 0; i < num; i++)
{
struct sockaddr_in * adr = (struct sockaddr_in *)&my_ifreqs[ i ].ifr_ifru.ifru_addr;
//Læg adressen bagest i objektets adresseliste
localHost.m_adresses.push_back((struct in_addr)adr->sin_addr);
}
#elif defined(_WIN32)
int i,s;
//Opret buffer med plads til 10 adresse strukturer
char buffer[sizeof(INT) + (sizeof(SOCKET_ADDRESS) * 10)];
DWORD returnedBytes;
LPSOCKET_ADDRESS_LIST adrList = (LPSOCKET_ADDRESS_LIST)buffer;
adrList->iAddressCount = 10;
if((s = socket(AF_INET,SOCK_DGRAM,0)) == INVALID_SOCKET)
{
LPVOID lpMsgBuf;
int err = WSAGetLastError();
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
err,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &lpMsgBuf,
0,
NULL
);
string description((char*)lpMsgBuf);
LocalFree( lpMsgBuf );
throw NetException(description);
}
//Spørg Windows om adresser
if(WSAIoctl(s, SIO_ADDRESS_LIST_QUERY, NULL, 0, buffer, sizeof(buffer), &returnedBytes,NULL, NULL) == SOCKET_ERROR)
{
closesocket(s);
LPVOID lpMsgBuf;
int err = WSAGetLastError();
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
err,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &lpMsgBuf,
0,
NULL
);
string description((char*)lpMsgBuf);
LocalFree( lpMsgBuf );
throw NetException(description);
}
//Frigiv vores socket
closesocket(s);
//Lav en adresse til loopback adressen
struct in_addr loopback;
loopback.S_un.S_addr = 0x0100007f;
//Læg loopback adressen i adresselisten
localHost.m_adresses.push_back(loopback);
//Læg alle adresser i localHost
for(i = 0; i < adrList->iAddressCount; i++)
{
if(adrList->Address[ i ].lpSockaddr->sa_family == AF_INET)
{
struct sockaddr_in * adrIn = (struct sockaddr_in *)adrList->Address[ i ].lpSockaddr;
//Læg adressen bagest i objektets adresseliste
localHost.m_adresses.push_back(adrIn->sin_addr);
}
}
#endif
//Returner vores localhost objekt
return localHost;
}
Nemt nok.
Som det ses lægger jeg manuelt loopback adressen 127.0.0.1 (hexadecimalt 0x0100007f) ind i Windows udgaven. Det er for at funktionen virker éns på begge platforme.
En revideret IP adresse lister
Dette bliver nok det korteste program, vi endnu har lavet:
#include <iostream>
#include "Net.h"
#include "IPAddress.h"
#include "Exception.h"
using namespace std;
int main(int argc, char ** argv)
{
try
{
netInit();
IPAddress localHost = IPAddress::getLocalHost();
cout << (string)localHost << endl;
for(UInt32 i = 0; i < localHost.numAdresses(); i++)
{
cout << localHost.getAddressString(i) << endl;
}
netRelease();
} catch (Exception & ex)
{
cout << "Kunne ikke skaffe informationer om localhost: " << ex << endl;
}
return 0;
}
Og skidtet virker:
[robert@codemachine Socket5]$ ./test
codemachine.the-playground.dk
127.0.0.1
192.168.1.3
192.168.57.1
[robert@codemachine Socket5]$
..og også under Windows:
E:\\Socket5>net5
windows
127.0.0.1
192.168.1.5
E:\\Socket5>
Programmet gik fra at fylde 111 linjer og indeholde grim arkitektur specifik kode til at fylde 26 linjer og være arkitektur uafhængig. Det kunne vist ikke være meget bedre.
Konklusion
Stol ALDRIG på at du har eksterne programmer at snylte på. Programmet bliver meget bedre, hvis det gøres "rigtigt".
Vi brugte et lidt grimt trick til at skabe ensartet output ved manuelt at indsætte 127.0.0.1 i Windows udgaven af koden, og det er ikke unormal praksis.
Held og lykke med din videre programmering.
Hvad synes du om denne artikel? Giv din mening til kende ved at stemme via pilene til venstre og/eller lægge en kommentar herunder.
Del også gerne artiklen med dine Facebook venner:
Kommentarer (9)
Nå, hvis der er så mange der giver den meget få points, så ville jeg enelig godt høre nogle argumenter. Jeg personligt syntes det er en smaddergod artikel, så jeg syntes ikke den fortjener at få så lav en rating. Anyways, et 5 tal fra mig. Thumbs up, og keep up the good work
Tak tak for de venlige ord og topkarakteren :-)
Ja et par kommentarer (selv negative) ville da være rart, hvis de er konstruktive. Jeg vil gerne skrive gode artikler og vil gerne vide, hvad jeg gør forkert nu.
Og selvfølgelig gerne idéer til nye artikler :-)
Op med den, elsker dine artikler Robert, altid så dejlig godt forklaret, hvorfor rater folk den så lavt? Keep up the good work!
virkelig god artikel, men du burde stærkt overveje at komme med noget forklaring i dine kode, da det ellers bliver meget tungt og til sin vis uforståeligt for nogle folk, hvilket også kan forkare den lave rating din ellers rigtig gode artikel har fået.
Nåda...ok...jeg synes ellers selv, at jeg skriver en hel del kommentarer i koden. Jeg skal prøve at gøre det bedre næste gang :-)
Der er en lille bug i sourcen:
int num, i, sock = socket(AF_INET,SOCK_DGRAM,0);
if(socket == 0)
{
throw NetException(strerror(errno));
}
skal være:
int num, i, sock = socket(AF_INET,SOCK_DGRAM,0);
if(sock == -1)
{
throw NetException(strerror(errno));
}
Iøvrigt tusind tak for en yderst anvendelig og meget god artikel. Jeg har taget mig den frihed at skrice en ren C udgave. Jeg har testet på Gentoo 2005.0 AMD64, og FC.
######## Ren C ######
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include <netdb.h>
#define HOSTNAME 0
#define FQDN 1
#define IPADDR 2
char *get_netid(int which)
{
char *hostname = NULL;
if (which == IPADDR) {
struct ifreq my_ifreqs[2];
struct ifconf my_ifconf;
my_ifconf.ifc_len = sizeof(my_ifreqs);
my_ifconf.ifc_req = my_ifreqs;
int sock = socket(AF_INET,SOCK_DGRAM,0);
if (-1 == sock)
goto out;;
if (ioctl(sock,SIOCGIFCONF,&my_ifconf) < 0) {
close(sock);
goto out;;
}
close(sock);
int num = my_ifconf.ifc_len / sizeof(struct ifreq);
if (!num)
goto out;
int i;
struct sockaddr_in *adr = NULL;
for (i = 0; i < num; i++) {
adr = (struct sockaddr_in *)&my_ifreqs.ifr_ifru.ifru_addr;
if (strcmp("127.0.0.1", inet_ntoa((struct in_addr)adr->sin_addr)))
break;
}
adr = (struct sockaddr_in *)&my_ifreqs.ifr_ifru.ifru_addr;
return strdup(inet_ntoa((struct in_addr)adr->sin_addr));
}
if ((which == HOSTNAME) || (which == FQDN)) {
hostname = (char*)malloc(sizeof(char)*HOST_NAME_MAX);
if (!hostname)
goto out;
if (gethostname(hostname, HOST_NAME_MAX))
goto out;
if (errno == EINVAL)
goto out;
if (which == HOSTNAME) {
size_t count = strcspn((const char*)hostname, ".'\\0'");
char *retv = (char*)malloc(sizeof(char)*(count+1));
if (!retv)
goto out;
strncpy(retv, (const char*)hostname, count);
retv[count] = '\\0';
free(hostname);
return retv;
}
}
if (which == FQDN) {
char *fqdn = NULL;
struct addrinfo *result, hints;
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_flags = AI_CANONNAME;
if (!getaddrinfo(hostname, NULL, &hints, &result)) {
fqdn = strdup(result->ai_canonname);
freeaddrinfo(result);
} else
goto out;
return fqdn;
}
out:
free(hostname);
return NULL;
}
int main(int argc, char ** argv)
{
printf("Hostname is: %s\\n", get_netid(HOSTNAME));
printf("FQDN is: %s\\n", get_netid(FQDN));
printf("IP address is: %s\\n", get_netid(IPADDR));
return 0;
}
Jeg bruger Dev-c++ og jeg kan ikke få nogen af dine scripts til at virke
?
Hvilke fejl får du Christian ?
Du skal være
logget ind for at skrive en kommentar.