ネットワークプログラミング(オリジナルソケット)
6768 ワード
元のソケットには、次の機能があります.
1、読み書きICMPv 4、IGMPv 4及びICMPv 6パケット.pingプログラムのように、元のソケットインタフェースを使用してICMPエコー要求を送信し、ICMPエコー応答を受信する.
2、特殊なIPv 4データを読み書きする.ほとんどのカーネル処理値は、1(ICMP)、2(IGMP)、6(TCP)、および17(UDP)のデータ・レポートです.プロトコル・フィールドは、他の値にもなります.
3、IP_HDRINCLソケットオプションを使用して、自分のIPv 4ヘッダを構築することができます.
元のソケットの作成
一般的に以下のステップに分けられます
1、2番目のパラメータはSOCK_RAWで、socket関数を呼び出して元のソケットを作成します.3番目のパラメータは一般的に0ではありません.protocolはIPPROTO_ICMPまたはIPPROTO_IGMPで使用できます.元のソケットを作成する権限はスーパーユーザーのみです.
2、IP_HDRINCLソケットのオプションを設定できます.
const int on = 1;
setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on));
3、元のセットに対してbind関数を呼び出すことができますが、よく使いません.これはローカルアドレスを設定するために使用され、ポート番号には意味がありません.
4、元のソケットでconnect関数を呼び出すこともできますし、あまり使われません.宛先アドレスを設定するだけで、ポートにも意味がありません.connectを呼び出すと、宛先アドレスが指定されているので、sendtoではなくwriteやsendを呼び出すことができます.
オリジナルソケット出力
出力には次のルールがあります.
1、通常出力はsendtoまたはsendmsgを呼び出し、目的のIPアドレスを指定して完了する.ソケットインタフェースが接続されている場合、write、writevまたはsendを使用することができる.
2、IP_HDRINCLが設定されていない場合、書き込みデータの先頭アドレスはIPヘッダの後の第1バイトであり、ヘッダプロトコルフィールドはsocket呼び出し時の第3パラメータとして記入される
3.IP_HDRINCLが設定されている場合、書き込みデータの先頭アドレスがIPヘッダの最初のバイトである場合、ユーザーが提供するデータサイズの値はヘッダのバイト数を含む必要がある.このプロセスは、識別フィールドと検査とフィールドのほか、プロセスによって設定される可能性がある.検査とは、カーネル計算によって埋め込まれる
4.外出インタフェースMTUを超えたパケットについては、カーネルをスライスする.
オリジナルソケット入力
受信した次のパケットは、元のソケットインタフェースに渡されるかどうか、次のルールがあります.
1、TCPとUDPパケットは元のセットインタフェースに渡さない
2.カーネルがICMPメッセージを処理した後、ほとんどのICMPパケットが元のソケットに伝達される
3、カーネルがIGMPメッセージを処理した後、すべてのIGMPパケットが元のソケットに渡される
4、カーネルが識別できないプロトコルフィールドのIPデータ報告はすべて元のインタフェースに伝達される.カーネルはこれらのパケットに対して唯一したのはIPヘッダのいくつかのフィールドを検査することである:IPバージョン、IPv 4ヘッダ検査と、ヘッダ長と目的のIPアドレス
5.データがフラグメント形式で到着した場合、パケットは元のすべてのフラグメントを再編成に到着した後、元のセットインタフェースに伝達する
次は元のソケットで書かれたPingのようなプログラムです.
1、読み書きICMPv 4、IGMPv 4及びICMPv 6パケット.pingプログラムのように、元のソケットインタフェースを使用してICMPエコー要求を送信し、ICMPエコー応答を受信する.
2、特殊なIPv 4データを読み書きする.ほとんどのカーネル処理値は、1(ICMP)、2(IGMP)、6(TCP)、および17(UDP)のデータ・レポートです.プロトコル・フィールドは、他の値にもなります.
3、IP_HDRINCLソケットオプションを使用して、自分のIPv 4ヘッダを構築することができます.
元のソケットの作成
一般的に以下のステップに分けられます
1、2番目のパラメータはSOCK_RAWで、socket関数を呼び出して元のソケットを作成します.3番目のパラメータは一般的に0ではありません.protocolはIPPROTO_ICMPまたはIPPROTO_IGMPで使用できます.元のソケットを作成する権限はスーパーユーザーのみです.
2、IP_HDRINCLソケットのオプションを設定できます.
const int on = 1;
setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on));
3、元のセットに対してbind関数を呼び出すことができますが、よく使いません.これはローカルアドレスを設定するために使用され、ポート番号には意味がありません.
4、元のソケットでconnect関数を呼び出すこともできますし、あまり使われません.宛先アドレスを設定するだけで、ポートにも意味がありません.connectを呼び出すと、宛先アドレスが指定されているので、sendtoではなくwriteやsendを呼び出すことができます.
オリジナルソケット出力
出力には次のルールがあります.
1、通常出力はsendtoまたはsendmsgを呼び出し、目的のIPアドレスを指定して完了する.ソケットインタフェースが接続されている場合、write、writevまたはsendを使用することができる.
2、IP_HDRINCLが設定されていない場合、書き込みデータの先頭アドレスはIPヘッダの後の第1バイトであり、ヘッダプロトコルフィールドはsocket呼び出し時の第3パラメータとして記入される
3.IP_HDRINCLが設定されている場合、書き込みデータの先頭アドレスがIPヘッダの最初のバイトである場合、ユーザーが提供するデータサイズの値はヘッダのバイト数を含む必要がある.このプロセスは、識別フィールドと検査とフィールドのほか、プロセスによって設定される可能性がある.検査とは、カーネル計算によって埋め込まれる
4.外出インタフェースMTUを超えたパケットについては、カーネルをスライスする.
オリジナルソケット入力
受信した次のパケットは、元のソケットインタフェースに渡されるかどうか、次のルールがあります.
1、TCPとUDPパケットは元のセットインタフェースに渡さない
2.カーネルがICMPメッセージを処理した後、ほとんどのICMPパケットが元のソケットに伝達される
3、カーネルがIGMPメッセージを処理した後、すべてのIGMPパケットが元のソケットに渡される
4、カーネルが識別できないプロトコルフィールドのIPデータ報告はすべて元のインタフェースに伝達される.カーネルはこれらのパケットに対して唯一したのはIPヘッダのいくつかのフィールドを検査することである:IPバージョン、IPv 4ヘッダ検査と、ヘッダ長と目的のIPアドレス
5.データがフラグメント形式で到着した場合、パケットは元のすべてのフラグメントを再編成に到着した後、元のセットインタフェースに伝達する
次は元のソケットで書かれたPingのようなプログラムです.
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <netdb.h>
#include <signal.h>
#include <sys/time.h>
#include <stdio.h>
#define BUFSIZE 1500
char recvbuf[BUFSIZE];
char sendbuf[BUFSIZE];
//int datalen;
char *host;
int nsent;
pid_t pid;
int sockfd;
int verbose;
void proc_v4(char *, ssize_t, struct timeval *);
//void proc_v6(char *, ssize_t, struct timeval *);
void send_v4();
//void send_v6();
void readloop();
void sig_alrm(int);
void tv_sub(struct timeval*, struct timeval*);
struct proto
{
void (*fproc)(char *, ssize_t, struct timeval*);
void (*fsend)(void);
struct sockaddr *sasend;
struct sockaddr *sarecv;
socklen_t salen;
int icmpproto;
}*pr;
char *sock_ntop(struct sockaddr *sa, socklen_t len)
{
char portstr[7];
static char str[128];
switch (sa->sa_family) {
case AF_INET:
{
struct sockaddr_in *sin = (struct sockaddr_in*)sa;
if (inet_ntop(AF_INET, &sin->sin_addr, str, sizeof(str)) == NULL) return NULL;
if (ntohs(sin->sin_port) != 0) {
snprintf(portstr, sizeof(portstr), "port=%d", ntohs(sin->sin_port));
strcat(str, portstr);
}
return str;
}
}
}
struct addrinfo *host_serv(const char *host, const char *serv, int family, int socktype)
{
struct addrinfo hints, *res;
int n;
bzero(&hints, sizeof(hints));
hints.ai_flags = AI_CANONNAME;
hints.ai_family = family;
hints.ai_socktype = socktype;
if ((n = getaddrinfo(host, serv, &hints, &res)) != 0) return NULL;
return res;
}
struct proto proto_v4 = {proc_v4, send_v4, NULL, NULL, 0, IPPROTO_ICMP};
int datalen = 56;
int main(int argc, char **argv)
{
int c;
struct addrinfo *ai;
pid = getpid();
signal(SIGALRM, sig_alrm);
ai = host_serv(argv[1], NULL, 0, 0);
pr = &proto_v4;
printf("ICMP_ECHO=%d
", ICMP_ECHO);
pr->sasend = ai->ai_addr;
pr->sarecv = calloc(1, ai->ai_addrlen);
pr->salen = ai->ai_addrlen;
readloop();
exit(0);
}
void readloop(void)
{
int size;
char recvbuf[BUFSIZE];
socklen_t len;
ssize_t n;
struct timeval tval;
sockfd = socket(pr->sasend->sa_family, SOCK_RAW, pr->icmpproto);
setuid(getuid());
size = 60 * 1024;
setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size));
sig_alrm(SIGALRM);
for (;;)
{
len = pr->salen;
n = recvfrom(sockfd, recvbuf, sizeof(recvbuf), 0, pr->sarecv, &len);
if (n < 0) {
if (errno == EINTR) continue;
else {
printf("recvfrom error:%s
", strerror(errno));
return;
}
}
gettimeofday(&tval, NULL);
(*pr->fproc)(recvbuf, n, &tval);
}
}
void tv_sub(struct timeval* out, struct timeval *in)
{
if ((out->tv_usec -= in->tv_usec) < 0) {
--out->tv_sec;
out->tv_usec += 1000000;
}
out->tv_sec -= in->tv_sec;
}
void proc_v4(char *ptr, ssize_t len, struct timeval *tvrecv)
{
int hlen1, icmplen;
double rtt;
struct ip *ip;
struct icmp *icmp;
struct timeval *tvsend;
ip = (struct ip*)ptr;
hlen1 = ip->ip_hl << 2;
icmp = (struct icmp*)(ptr + hlen1);
if ((icmplen = len - hlen1) < 8) {
fprintf(stderr, "icmp len error
");
return;
}
if (icmp->icmp_type == ICMP_ECHOREPLY) {
if (icmp->icmp_id != pid) return;
if (icmplen < 16) {
fprintf(stderr, "icmplen (%d) < 16
", icmplen);
return;
}
tvsend = (struct timeval *)icmp->icmp_data;
tv_sub(tvrecv, tvsend);
rtt = tvrecv->tv_sec * 1000 + tvrecv->tv_usec / 1000;
printf("%d bytes from %s:seq=%u, ttl=%d, rtt=%.3f ms
",
icmplen, sock_ntop(pr->sarecv, pr->salen), icmp->icmp_seq, ip->ip_ttl, rtt);
} else if (verbose) {
printf("%d bytes from %s:type=%d, code=%d
", icmplen,
sock_ntop(pr->sarecv, pr->salen), icmp->icmp_type, icmp->icmp_code);
}
}
void sig_alrm(int signo)
{
(*pr->fsend)();
alarm(1);
return;
}
unsigned short in_cksum(unsigned short *addr, int len)
{
int nleft = len;
int sum = 0;
unsigned short *w = addr;
unsigned short answer = 0;
while (nleft > 1)
{
sum += *w++;
nleft -= 2;
}
if (nleft == 1)
{
*(unsigned char *)(&answer) = *(unsigned char *)w;
sum += answer;
}
sum = (sum >> 16) + (sum & 0xffff);
sum += (sum >> 16);
answer = ~sum;
return answer;
}
void send_v4()
{
int len;
struct icmp *icmp;
icmp = (struct icmp *)sendbuf;
icmp->icmp_type = ICMP_ECHO;
icmp->icmp_code = 0;
icmp->icmp_id = pid;
icmp->icmp_seq = nsent++;
gettimeofday((struct timeval*)icmp->icmp_data, NULL);
len = 8 + datalen;
icmp->icmp_cksum = 0;
icmp->icmp_cksum = in_cksum((unsigned short*)icmp, len);
sendto(sockfd, sendbuf, len, 0, pr->sasend, pr->salen);
}