Cソケットプログラミング
・IFのIndex/IPアドレス/Macアドレス情報を取得する
・★ioctlを連続でやると変な数字になる?原因は後々調べてみる
参考
- ioctlについて
https://euniclus.com/article/get-interface-information/ - IPアドレスの変数の構造について
https://qiita.com/edo_m18/items/2cf4e538bf8ba7e82caa - macアドレスの変数の構造について
https://wireless-network.net/macaddress-viewer/
//ルータのチューニング用パラメータ
typedef struct {
char *Device1;
char *Device2;
int DebugOut;
char *NextRouter;
}PARAM;
PARAM Param={"eth0","eth1",1,"192.168.0.254"};
//インタフェースの情報(PARAMに基づいてデータを格納)
typedef struct {
int soc;
u_char hwaddr[6];
struct in_addr addr,subnet,netmask;
}DEVICE;
int main(int argc,char *argv[]){
printf("----Device Check----\n");
// Device1のチェック
struct ifreq ir;
memset(&ir,0,sizeof(struct ifreq));
int soc = socket(AF_INET, SOCK_STREAM, 0);
snprintf(ir.ifr_name, 100, "%s", Param.Device1);
//IF Index取得
ioctl(soc, SIOCGIFINDEX, &ir);
printf("Device1 = %s \n",Param.Device1);
printf("Device1 Number = %d\n", ir.ifr_ifindex);
//IP Address取得
ioctl(soc, SIOCGIFADDR, &ir);
printf("Device1 IPAddress = %s\n", inet_ntoa(((struct sockaddr_in *)&ir.ifr_addr )->sin_addr));
//Mac Address取得
ioctl(soc, SIOCGIFHWADDR, &ir);
printf("Device1 MacAddress = %02x:%02x:%02x:%02x:%02x:%02x",
(uint8_t)ir.ifr_hwaddr.sa_data[0],
(uint8_t)ir.ifr_hwaddr.sa_data[1],
(uint8_t)ir.ifr_hwaddr.sa_data[2],
(uint8_t)ir.ifr_hwaddr.sa_data[3],
(uint8_t)ir.ifr_hwaddr.sa_data[4],
(uint8_t)ir.ifr_hwaddr.sa_data[5]
);
printf("\n");
return 0;
}
構造体の違い
・大きく以下の種類
- sockaddr
- sockaddr_in
- in_addr
- ifreq
・sockaddr_inはsockaddrのLinuxver。
・in_addrはIPv4アドレスを表すためのシンプルな構造体
→ルータ自作本では、最終的にin_addrの値で扱っている
(in_addrの方が扱いやすいから?)
⇄ioctlではsockaddr_inになっているので、それを変換してあげたりしている?
struct sockaddr_in {
u_char sin_len;
u_char sin_family;
u_short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
struct in_addr {
u_int32_t s_addr;
};
(参考)
関数の違い
・大きく二つ
- inet_ntop 指定されたアドレスファミリ(af)で指定されたアドレス構造体(src)を文字列(dst)に変換する
- 返り値もそのまま文字列のdstになる?
-
const char *inet_ntop(int af, const void *src,char *dst, socklen_t size);
- inet_ntoa IPv4アドレスを文字列で表示する(in_addr構造体のみが使える?)
- char *inet_ntoa(struct in_addr in);
sleep関数を実行すると前後の出力がうまく出力されない
- 以下のようなコードを実行しても、startとendが一斉に表示されてしまうことがある
printf("start");
sleep(5);
printf("end");
- これはストリーム(ターミナル?)によって出力バッファリングが行われるためである。
- 改行コードが入るまで出力されない
対策1:改行コードを前後に入れる
printf("start\n");
sleep(5);
printf("end\n");
対策2:fflush関数を実行しておく
printf("start");
fflush(stdout);
sleep(5);
printf("end");
fflush(stdout);
参考
時間計測
# include <unistd.h>
# include <time.h>
int main(){
int nsec;
double d_sec;
struct timespec start_time, end_time;
printf("start\n");
fflush(stdout);
/* 処理開始前の時間を取得 */
clock_gettime(CLOCK_REALTIME, &start_time);
/* 時間を計測する処理(ここから) */
/* 時間を計測する処理(ここまで) */
printf("end\n");
fflush(stdout);
/* 処理開始後の時間とクロックを取得 */
clock_gettime(CLOCK_REALTIME, &end_time);
/* 処理中の経過時間を計算 */
sec = end_time.tv_sec - start_time.tv_sec;
nsec = end_time.tv_nsec - start_time.tv_nsec;
d_sec = (double)sec
+ (double)nsec / (1000 * 1000 * 1000);
/* 計測時間の表示 */
printf(
"time:%f\n", d_sec
);
}
Poll関数の使い方
- poll関数は、複数のファイルディスクリプタの状態を一度に監視し、読み込み可能、書き込み可能、エラー発生などの状態変化を検出するために使用されます。
-
- poll関数の引数は以下のようになっています。
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
fds: 監視対象のファイルディスクリプタと、そのファイルディスクリプタに対する監視設定を保持するpollfd構造体の配列へのポインタです。
nfds: 監視するファイルディスクリプタの数です。
timeout: poll関数のタイムアウト値をミリ秒単位で指定します。負の値を指定すると、無限に待機します。0を指定すると、即座に戻ります。
具体的には、プログラマは以下の手順でpoll関数を使用します。
- pollfd構造体を初期化する。
- 監視したいファイルディスクリプタをpollfd構造体に設定する。
- poll関数を呼び出し、監視を開始する。
- poll関数が戻ると、pollfd構造体のreventsフィールドには、発生したイベントの情報がセットされる。
- reventsフィールドをチェックし、発生したイベントに応じた処理を行う。
また、poll関数は、エラーが発生した場合には-1を返し、errnoにエラーコードをセットします。成功した場合には、0以上の値を返します。この戻り値は、reventsフィールドをチェックして、発生したイベントの数を取得するために使用されます。
Pollをネットワークプログラミングに適用する方法
- fdを監視したいデバイスのソケットにすれば良い
struct pollfd interface[2];
interface[0].fd=Device[0].soc;
interface[0].events=POLLIN|POLLERR;
interface[1].fd=Device[1].soc;
interface[1].events=POLLIN|POLLERR;
- poll関数で、pollfdを監視する
ret = poll(interface,2,1000); - -1はソケットのエラー
- 0はタイムアウト
- 1以上が返ってきたら通信があったということ。
- どのソケットなのかはfor文で調べる
while (1)
{
ret = poll(interface,2,1000);
if(ret == -1){
perror("poll");
}
else if(ret==0){
//何もしない
//printf("Timeout!\n");
}
else{
//どっちのinterfaceが返したのか確認
for(int i=0;i<2;i++){
if(interface[i].revents & (POLLIN|POLLERR)){
if((size=read(Device[i].soc,buf,sizeof(buf)))<=0){
perror("read");
}
else{
AnalyzePacket(i,buf,size);
}
}
}
}
}
ARP構造体の中身
struct ether_arp {
struct arphdr ea_hdr; // ARPヘッダー
u_char arp_sha[ETHER_ADDR_LEN]; // 送信元MACアドレス
u_char arp_spa[4]; // 送信元IPアドレス
u_char arp_tha[ETHER_ADDR_LEN]; // 宛先MACアドレス
u_char arp_tpa[4]; // 宛先IPアドレス
};
pthread_cond_tは、スレッド同期のための条件変数を表す構造体です。条件変数は、スレッドが特定の条件が満たされるまで待機するために使用されます。以下に、pthread_cond_tの基本的な使い方を示すサンプルコードを示します。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int count = 0;
void *increment_count(void *arg) {
for (int i = 0; i < 5; i++) {
pthread_mutex_lock(&mutex);
count++;
printf("Incremented count to %d\n", count);
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
sleep(1);
}
return NULL;
}
void *wait_for_count(void *arg) {
pthread_mutex_lock(&mutex);
while (count < 3) {
printf("Waiting for count to be at least 3...\n");
pthread_cond_wait(&cond, &mutex);
}
printf("Count is now at least 3, continuing...\n");
pthread_mutex_unlock(&mutex);
return NULL;
}
int main() {
pthread_t thread1, thread2;
pthread_create(&thread1, NULL, increment_count, NULL);
pthread_create(&thread2, NULL, wait_for_count, NULL);
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
printf("Exiting program.\n");
return 0;
}
このサンプルコードでは、2つのスレッドが作成されています。1つのスレッドは、count変数を5回インクリメントし、pthread_cond_signal()を呼び出して条件変数をシグナルします。もう1つのスレッドは、count変数が3以上になるまで待機し、pthread_cond_wait()を呼び出して条件変数を待ちます。
pthread_cond_wait()は、mutexを解除して、条件変数がシグナルされるまでスレッドをブロックします。pthread_cond_signal()は、待機中のスレッドを1つだけ起こし、条件変数をシグナルします。
→ pthread_cond_signal()が呼ばれないとpthread_cond_wait()はずっと止まる
このサンプルコードでは、スレッド間での競合状態を避けるために、pthread_mutex_tを使用してクリティカルセクションを保護しています。また、pthread_cond_wait()は、mutexを自動的にロックし、pthread_cond_signal()は、mutexをロックしてから呼び出す必要があることに注意してください。
inet_ntoaとinet_ntop
inet_ntoaとinet_ntopは、どちらもIPアドレスを文字列に変換する関数ですが、引数と戻り値の形式が異なります。
inet_ntoaは、32ビットのIPアドレスを表す数値を受け取り、ドットで区切られたIPv4アドレスの文字列を返します。例えば、引数が 16820416 であれば、戻り値は "1.2.3.4" になります。
一方、inet_ntopは、IPv4またはIPv6アドレスをバイト列の形式で受け取り、ドットで区切られたIPv4アドレスの文字列またはIPv6アドレスの文字列を返します。この関数は、IPv4とIPv6の両方に対応しており、引数として与えたバイト列の形式に応じて、適切なフォーマットでアドレスを変換します。
例えば、IPv4アドレスの場合は、引数として 4 バイトのバッファを受け取り、そのバッファに格納されたIPアドレスをドットで区切られた文字列に変換します。IPv6アドレスの場合は、引数として 16 バイトのバッファを受け取り、そのバッファに格納されたIPv6アドレスを16進数の文字列に変換します。
つまり、inet_ntoaはIPv4アドレスを数値から文字列に変換するための関数であり、inet_ntopはIPv4またはIPv6アドレスをバイト列から文字列に変換するための関数です。