【Python】ipv4の操作

import socket

host = socket.gethostname()
print(host)

ip = socket.gethostbyname(host)
print(ip)

$ python3 test.py
vagrant
127.0.1.1

$ pip3 install netaddr

from netaddr import *

ip = IPAddress('127.0.1.1')
print(ip.version)

print(IPAddress('127.0.1.1').ipv6())

rustの場合、is_ipv4でipv4の判定、is_ipv6判定などができる。
なるほど、ipv4, v6は深掘りする必要がありそう。

【Rust】UDPのやり取り

ncコマンドの場合、”-u”がUDPを指定している
$ nc -u -l 8888
$ echo “hoge” | nc -u 127.0.0.1 8888

use std::net::UdpSocket;

fn main() -> std::io::Result<()> {
    {
        let socket = UdpSocket::bind("127.0.0.1:8080")?;

        let mut buf = [0; 100];
        let (amt, src) = socket.recv_from(&mut buf)?;

        let buf = &mut buf[..amt];
        buf.reverse();
        socket.send_to(buf, &src)?;
    }
    Ok(())
}

$ echo “hoge” | nc -u 127.0.0.1 8080

egoh

UDPのbindはできるけど、レスポンスが何かおかしい…

use std::net::UdpSocket;
use std::thread;
use std::str;

fn main() -> std::io::Result<()> {
    {
        let socket = UdpSocket::bind("127.0.0.1:8080")?;
        let mut buf = [0; 2048];
        loop {
            match socket.recv_from(&mut buf) {
                Ok((buf_size, src_addr)) => {
                    thread::spawn(move || {
                        let buf = &mut buf[..buf_size];
                        let req_msg = str::from_utf8(&buf).unwrap();
                        println!("request message: {:?}", req_msg);
                    });
                },
                Err(e) => {
                    println!("couldn't receive request: {:?}", e);
                }
            }
        }
    }
}

Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.63s
Running `target/debug/app`
request message: “hoge\n”

なるほど、TCPだけでなく、UDPも割と簡単にできるのね。。音声、動画streamingはUDPが優れているが…

【Rust】AxumでWeb Socketの処理を行う

ws モジュールを使用する
https://docs.rs/axum/latest/axum/extract/ws/index.html

#[tokio::main]
async fn main() {
    let app = Router::new().route("/ws", any(handler));
    // Start the server
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app.into_make_service_with_connect_info::<SocketAddr>()).await.unwrap();
}
async fn handler(ConnectInfo(addr): ConnectInfo<SocketAddr>, ws: WebSocketUpgrade) -> Response {
    println!("{}", addr);
    ws.on_upgrade(handle_socket)
}

async fn handle_socket(mut socket: WebSocket) {
    while let Some(msg) = socket.recv().await {
        let mut msg = if let Ok(msg) = msg {
            msg
        } else {
            return;
        };
        if socket.send(msg).await.is_err() {
            // client disconnected
            return;
        }
    }
}

クライアントサイド

    <textarea id="output" cols="50" rows="10" readonly></textarea><br>
    <input type="text" id="input" placeholder="Type a message" />
    <button onclick="sendMessage()">Send</button>
<script>
        const ws = new WebSocket("ws://192.168.33.10:3000/ws");
        const output = document.getElementById("output");
        const input = document.getElementById("input");
        input.value = "hello";
        
        ws.onmessage = (event) => {
            output.value += `\n${event.data}`;
        };
        function sendMessage() {
            const message = input.value;
            ws.send(message);
            input.value = "";
        }
    </script>

axumでも問題なくできることがわかりました。
なるほど、ちょっと理解しました。

【Rust】TCP Stream

なるほど、接続状態が維持されているのがわかる。

use std::net::{TcpListener, TcpStream};
use std::thread;

use std::io::{Read,Write,Error};

fn sendRecv(mut stream: TcpStream) -> Result<(), Error>
{
    println!("Connection from: {}", stream.peer_addr()?);
    let mut buf = [0; 512];
    loop {
        let byte_read = stream.read(&mut buf)?;
        if byte_read == 0{return Ok(());} else {
            let msg = format!("MESSAGE:");
            stream.write(msg.as_bytes()).expect("SEND FAILURE!!!");
        }

        stream.write(&buf[..byte_read])?;
    }
}

fn main() {
    let args: Vec<String> = std::env::args().collect();

    let coninfo = args[1].to_string() + ":" + &args[2];
    let listener = TcpListener::bind(coninfo).expect("COuld not bind");
    for stream in listener.incoming(){
        match stream {
            Err(e) => {eprintln!("failed: {}", e)}
            Ok(stream) => 
            {
                thread::spawn(move || {
                    sendRecv(stream).unwrap_or_else(|error| eprintln!("{:?}", error));
                });
            }
        }
    }
    println!("hello world");
}

Running `target/debug/sample 192.168.33.10 8000`
Connection from: 192.168.33.10:55828

$ nc 192.168.33.10 8000
hello
MESSAGE:hello

[TCP/IP] ソケットAPIリファレンス

### データ構造体
sockaddr構造体: 汎用のアドレス構造体
sockaddr_in構造体: インターネットアドレスファミリの構造体

### ソケットのセットアップ
socket(): TCPソケットまたはUDPソケットを作成
bind(): ローカルIPアドレスとローカルポートをソケットに割り当て
getsockname(): ソケットのローカル情報をsockaddr構造体に返す

### ソケットの接続
connect(): ローカルソケットと外部アドレスで指定したリモートソケットとの間にコネクションを確立
listen(): (ストリーム/TCPのみ): ソケットが接続待ち状態であることを示す
accept(): (ストリーム/TCPのみ): 既にIPアドレスとポートにバインドされているソケットへの接続要求を受け付ける
getpeername(): ソケットのリモート情報をsockaddr構造体に返す

### ソケットの通信
send(): バッファ内のバイトを指定のソケット経由で送信
sendto(): バッファ内のバイトを指定のソケット経由で送信
recv(): ソケットで受信したデータを指定のバイト数以下だけ指定のバッファ領域にコピーする
recvfrom(): ソケットで受信したデータを最大でも指定のバイト数だけ指定の場所にコピーする
close(): ソケットでの通信を終了する これ以上受信ができないというマークがつけられる
shutdown(): ソケットでの通信を終了する

### ソケットの制御
getsockopt(): ソケットのオプションを取得 オプションを使うとデフォルトの動作を変更できる
setsockopt(): ソケットのオプションを変更

### バイナリ/文字列お変換
inet_ntoa(): バイナリ表記(ネットワークバイト順)のIPアドレスをドット10進表記に変換
inet_addr():ドット10進表記のIPアドレスをバイナリ表記のIPアドレスに変換

### ホスト情報とサービス情報
gethostname(): 指定のバッファにローカルホスト名を返却
gethostbyname(): ホスト名を指定すると、gethostbyname()はそのホストの情報が記載されたhostent構造体を返す
gethostbyaddr(): IPアドレスを指定すると、gethostbyaddr()はそのIPアドレスを持つホストの情報が記載されたhostent構造体を返す
getservbyname(): サービス名とそのサービスを実装するプロトコルを指定すると、そのサービス情報が記載されたservent構造体を返す
getservbyport(): サービス情報が記載されたservent構造体を返す
hostent構造体
servent構造体

[TCP/IP] ドメインネームサービス

TCP/IPでは通信相手となるエンドポイントをIPとポートで表す
ほとんどのソケットAPIはネームサービスにアクセスできるようになっている
ネームサーバが利用する情報源はさまざまだが、主なものにDNS(分散データベース)とローカル設定データベースの2つがある
TCP、UDPがデータベースから情報を取得する手順はDNSプロトコルとして定義されている。

### 名前とIPの対応付け
struct hostent *gethostbyname(const char *hostName)
gethostbynameはhostent構造体を返却する

struct hostent {
char *h_name;
char **h_aliases;
int h_addrtype;
int h_length;
char **h_addr_list;
}

ResolveName.c

#include <stdio.h>
#include <netdb.h>

unsigned long ResolveName(char name[]){
    struct hostent *host;

    if((host =gethostbyname(name)) ~~ NULL) {
        fprintf(stderr, "fethostbyname() failed");
        exit(1);
    }

    return *((unsigned long *) host->h_addr_list[0]);
}

こちらを使用するには次のように書く
echoServAddr.sin_addr.s_addr = ResolveName(servIP);

ip addressからhost nameを取得するには、gethostbyaddr()で取得できる
struct hostent *gethostbyaddr(const char *address, int addressLength, int addressFamily)

### 名前によるサービス情報の検索
struct servent *getservbyname(const char *serviceName, const char *protocol)

servent構造体
struct servent {
char *s_name;
char **s_aliases;
int s_sport;
char *s_proto;
}

#include <stdio.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdlib.h>

unsigned short ResolveService(char service[], char protocol[]){

    struct servent *serv;
    unsigned short port;

    if((port=atoi(service)) == 0){
        if((serv = getservbyname(service, protocol)) == NULL) {
            fprintf(stderr, "getservbyname() failed");
            exit(1);
        }
        else {
            port = htons(port);
        }
        return port;
    }
}

[TCP/IP] マルチキャスト

マルチキャストはUDPユニキャストと似ているが、アドレスにメッセージ受信者の集合を指定する。
MutlicastSender.c

#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

void DieWithError(char *errorMessage);

int main(int argc, char *argv[]){
    int sock;
    struct sockaddr_in multicastAddr;
    char *multicastIP;
    unsigned short multicastPort;
    char *sendString;
    unsigned char mutlicastTTL;
    unsigned int send StringLen;

    if((argc < 4) || (argc > 5)){
        fprintf(stderr, "Usage: %s <Multicast Address><Port><Send String>[<TTL>]\n", argv[0]);
        exit(1);
    }

    multicastIP = argv[1];
    multicastPort = atoi(argv[2]);
    sendString = argv[3];

    if(argc == 5)
        multicastTTL = atoi(argv[4]);
    else
        multicastTTL = 1;

    if((sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0)
        DieWithError("socket() failed");

    if(setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, (void *) &multicastTTL, sizeof(multicastTTL)) < 0)
        DieWithError("setsockopt() failed");

    memset(&broadcastAddr, 0, sizeof(broadcastAddr));
    broadcastAddr.sin_family = AF_INET;
    broadcastAddr.sin_addr.s_addr = inet_addr(multicastIP);
    broadcastAddr.sin_port = htons(broadcastPort);

    sendStringLen = strlen(sendString);
    for(;;){
        if (sendto(sock, sendString, sendStringLen, 0, (struct sockaddr *) &multicastAddr, sizeof(multicastAddr), sizeof(multicastAddr)) != sendStringLen)
            DieWithError("sendto() send a different number of bytes than expected.");
        sleep(3);
    }
}
#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define MAXRECVSTRING 255

void DieWithError(char *errorMessage);

int main(int argc, char *argv[]){
    int sock;
    struct sockaddr_in multicastAddr;
    char *multicastIP;
    unsigned short multicastPort;
    char recvString[MAXRECVSTRING+1];
    int recvStringLen;
    struct ip_mreq multicastRequest;

    if(argc != 3){
        fprintf(stderr, "Usage: %s <Multicast IP><Port>]\n", argv[0]);
        exit(1);
    }

    multicastIP = argv[1];
    multicastPort = atoi(argv[2]);

    if((sock = socket(AF_INET, SOCK_GRAM, IPPROTO_UDP)) < 0)
        DieWithError("socket() failed");

    memset(&broadcastAddr, 0, sizeof(broadcastAddr));
    broadcastAddr.sin_family = AF_INET;
    broadcastAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    broadcastAddr.sin_port = htons(multicastPort);

    if(bind(sock, (struct sockaddr *) &multicastAddr, sizeof(multicastAddr)) < 0)
        DieWithError("bind() failed");

    muticastRequest.imr_multiaddr.s_addr = inet_addr(multicastIP);
    multicastRequest.imr_interface.s_addr = htonl(INADDR_ANY);

    if(setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void *) &multicastRequest, sizeof(multicastRequest))< 0)
        DieWithError("setsockopt() failed");

    if((recvStringLen = recvfrom(sock, recvString, MAXRECVSTRING, 0, NULL, 0)) < 0)
        DieWithError("recvfrom() failed");

    recvString[recvStringLen] = 
    printf("Recieved: %s\n", recvString);

    close(sock);
    exit(0);

}

ブロードキャストとマルチキャストのどちらを使用するかは受信を要求するホストの割合や通信相手に関する情報の有無などいくつかの条件を決める必要がある

[TCP/IP] ブロードキャストとマルチキャスト

1対1の通信はユニキャストと呼ぶ
接続帯域で多数に送信するには送信帯域を使い切ってしまう可能性があるが、データを一度だけコピーし、ネットワークに任せてしまえば、多数の相手にストリーム配信できる。コピー方法はブロードキャストとマルチキャストがある。
ブロードキャストは、ネットワークに接続する全てのホストがメッセージをコピー
マルチキャストは条件を満たす場合にのみ送信される
ブロードキャスト、マルチキャスト共に利用できるのはUDPソケットのみ。

### ブロードキャスト
ユニキャストとの違いはアドレス形式(255,255,255,255)

#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string>
#include <unistd.h>

void DieWithError(char *errorMessage);

int main(int argc, char *argv[]){
    
    int sock;
    struct sockaddr_in broadcastAddr;
    char *broadcastIP;
    unsigned short broadcastPort;
    char *sendString;
    int broadcastPermission;
    unsigned int sendStringLen;

    if (argc < 4){
        fprintf(stderr, "Usage: %s <IP Address> <Port> <Send String>\n", argv[0]):
        exit(1);
    }

    broadcastIP = argv[1];
    broadcastPort = atoi(argv[2]);
    sendString = argv[3];

    if((sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0)
        DieWithError("socket() failed");

    broadcastPermission = 1;
    if(setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (void *) &broadcastPermission, sizeof(broadcastPermission)) < 0)
        DieWithError("setsockopt() failed");

    memset(&broadcastAddr, 0, sizeof(broadcastAddr));
    broadcastAddr.sin_family = AF_INET;
    broadcastAddr.sin_addr.s_addr = inet(broadcastIP);
    broadcastAddr.sin_port = htons(broadcastPort);

    sendStringLen = strlen(sendString);
    for(;;){
        if(sendto(sock, sendString, sendStringLen, 0, (struct sockaddr *) &broadcastAddr, sizeof(broadcastAddr)) != sendStringLen)
            DieWithError("sendto() sent a different number of bytes than expected");

        sleep(3);
    }

BroadcastReciver.c

#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define MAXRECVSTRING 255

void DieWithError(char *errorMessage);

int main(int argc, char *argv[]){

    int sock;
    struct sockaddr_in broadcastAddr;
    unsigned int broadcastPort;
    char recvString[MAXRECVSTRING+1];
    int recvStringLen;

    if (argc != 2) {
        fprintf(stderr, "Usage: %s <Broadcast Port>\n", argv[0]);
        exit(1);
    }

    memset(&broadcastAddr, 0, sizeof(broadcastAddr));
    broadcastAddr.sin_family = AF_INET;
    broadcastAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    broadcastAddr.sin_port = htons(broadcastPort);

    if(bind(sock, (struct sockaddr *) &broadcastAddr, sizeof(broadcastAddr)) < 0)
        DieWtihError("bind() failed");

    if((recvStringLen = recvfrom(sock, recvString, MAXRECVSTRING, 0, NULL, 0)) < 0)
        DieWtihError("recvfrom() failed");

    recvString[recvStringLen] = '\0';
    printf("Received: %s\n", recvString);

    close(sock);
    exit(0);

}

[TCP/IP] 多重化

Unixではselect関数を使用して、ペンディング中のI/Oをチェックするディスクリプタのリストを指定できる。
int select(int maxDescPlus1, fd_set *readDescs, fd_set *writeDescs, fd_set *exceptionDescs, struct timeval *timeout)

FD_ZERO(fd_set *descriptorVector) : fd_setにあるディスクリプタを全て削除
FD_CLR(int descriptor, fd_set *descriptorVector) : fd_setから削除
FD_SET(int descriptor, fd_set *descriptorVector) : 追加
FD_ISSET(int descriptor, fd_set *descriptorVector)

#include "TCPEchoServer.h"
#include <sys/time.h>
#include <fcntl.h>

int main(int argc, char *argv[]){
    int *servSock;
    int maxDescriptor;
    fd_set sockSet;
    long timout;
    struct timeval setTimeout;
    int running = 1;
    int noPorts;
    int port;
    unsigned short portNo;

    if(argc < 3) {
        fprintf(stderr, "Usage: %s <Timeout(secs.)><Port 1>... \n", argv[0]);
        exit(1);
    }

    timeout = atol(argv[1]);
    noPorts = argc - 2;

    servSock = (int *)malloc(noPorts * sizeof(int));

    maxDescriptor = -1;

    for (port = 0; port < noPorts; port++){
        portNo = atoi(argv[port + 2]);

        servSock[port] = CreateTCPServerSocket(portNo);

        if(servSock[port] > maxDescriptor)
            maxDescriptor = servSock[port];
    }

    printf("Starting server: Hit return to shutdown\n");
    while(running) {
        FD_ZERO(&sockSet);

        FD_SET(STDIN_FILENO, &sockSet);
        for(port = 0; port < noPorts; port++)
            FD_SET(servSock[port], &sockSet);

        setTimeout.tv_sec = timeout;
        setTimeout.tv_usec = 0;

        if(select(maxDescriptor + 1, &sockSet, NULL, NULL, &selTimout) == 0)
            printf("No echo request for %ld secs... Server still alive\n", timeout);
        else {
            if(FD_ISSET(STDIN_FILENO, &socket)){
                printf("Shutting down server\n");
                getchar();
                running = 0;
            }
            for (port = 0; port < noPorts; port++)
                if(FD_ISSET(servSock[port], &sockSet)){
                    printf("Request on port %d:", port);
                    HandleTCPClient(AcceptTCPConnection(servSock[port]));
                }
        }
    }
    for(port = 0; port < noPorts; port++)
        close(servSock[port]);

    free(servSock);
    exit(0);
}

[TCP/IP] 制限付きマルチタスク

サーバが作成するプロセスの数を制限する
L あらかじめ設定された数だけプロセスを作成する

#include "TCPEchoServer.h" /* ヘッダーファイルをインクルード */

void ProcessMain(int servSock); /* プロセスのメインプログラム */

int main(int argc, char *argv[]) {
    int servSock;
    unsigned short echoServPort;
    pit_t processID;
    unsigned int processLimit;
    unsigned int processCt;

    if (argc != 3) {
        fprintf(stderr, "Usage: %s <Server Port> <FORK LIMIT>\n", argv[0]);
        exit(1);
    }

    echoServPort = atoi(argv[1]);
    processLimit = atoi(argv[2]);

    servSock = CreateTCPServerSocket(echoServPort);

    for(processCt=0; processCt < processLimit; processCt++)
        if((processID == fork()) < 0)
            DieWithError("fork() failed");
        else if(processID == 0)
            ProcessMain(servSock);

    exit(0);
}

void ProcessMain(int servSock) {

    int clntSock;

    for(;;) /* 処理を繰り返し実行 */
    {
        clntSock = AcceptTCPConnection(servSock);
        printf("with child process: %d\n", (unsigned int) getpid());
        HandleTCPClient(clntSock);
    }
}

指定されたプロセスしか生成しないため、スケジューリングによるオーバーヘッドを削減できる。