[Dev] 同じポートをIPv4とIPv6で待ち受けする。

in #japanese3 days ago

検証内容

  • 同じポートをIPv4とIPv6で待ち受けする。
    どちらも同じポート3000番でプログラムを起動。(別々のプロセス)
  • bun/nodejs/goでそれぞれ実行する。
    ➡️bun失敗。IPv6(だけではなく)とIPv4を一緒にオープンしているため。
    ➡️nodejs失敗。IPv6(だけではなく)とIPv4を一緒にオープンしているため。
    ➡️go成功。(TCP4、TCP6を指定してオープン)

bun

cat server1.ts
import { serve } from "bun";

// 第1引数: hostname
// 第2引数: port
const hostname = Bun.argv[2] || "::";
const port = Number(Bun.argv[3] || 3000);

console.log(`listen: [${hostname}]:${port}`);

serve({
  hostname,
  port,

  fetch: async (req) => {
    const url = new URL(req.url);

    console.log("url.pathname:", url.pathname);

    if (url.pathname === "/hivemind/") {
      const body = JSON.stringify({
        jsonrpc: "2.0",
        id: 0,
        method: "hive.db_head_state",
        params: {}
      });

      const backend = await fetch("http://localhost:8888", {
      //const backend = await fetch("http://113.144.132.118:8888", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "Host": "steememory.com",
        },
        body,
      });

      const data = await backend.text();

      return new Response(data, {
        status: backend.status,
        headers: {
          "Content-Type": "application/json",
        },
      });
    }

    return new Response("Not Found", { status: 404 });
  },
});

🪣IPv4で待ち受ける
image.png

🪣IPv6で待ち受ける
失敗する。IPv6(だけではなく)とIPv4を一緒にオープンしているため。😭
image.png


📌0.0.0.0でオープンした場合
tcp4のみオープン。
image.png
📌::でオープンした場合
tcp4とtcp6の両方をオープン。🤔
image.png


nodejs

cat server1.js
import http from "http";

// 第1引数: hostname
// 第2引数: port
const hostname = process.argv[2] || "::";
const port = Number(process.argv[3] || 3000);

console.log(`listen: [${hostname}]:${port}`);

const server = http.createServer(async (req, res) => {
  const url = new URL(req.url, `http://${req.headers.host}`);

  console.log("url.pathname:", url.pathname);

  if (url.pathname === "/hivemind/") {
    const body = JSON.stringify({
      jsonrpc: "2.0",
      id: 0,
      method: "hive.db_head_state",
      params: {}
    });

    try {
      const backendRes = await fetch("http://localhost:8888", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "Host": "steememory.com",
        },
        body,
      });

      const data = await backendRes.text();

      res.writeHead(backendRes.status, {
        "Content-Type": "application/json",
      });

      res.end(data);
      return;

    } catch (err) {
      res.writeHead(500, {
        "Content-Type": "application/json",
      });
      res.end(JSON.stringify({ error: String(err) }));
      return;
    }
  }

  res.writeHead(404, {
    "Content-Type": "text/plain",
  });
  res.end("Not Found");
});

// listen
server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});

🪣IIPv4で待ち受ける
image.png

🪣IIPv6で待ち受ける
失敗する。IPv6(だけではなく)とIPv4を一緒にオープンしているため。😭
image.png


GO

cat server1.go
package main

import (
        "bytes"
        "io/ioutil"
        "log"
        "net"
        "net/http"
        "os"
)

func main() {
        hostname := "::"
        port := "3000"

        if len(os.Args) > 1 {
                hostname = os.Args[1]
        }
        if len(os.Args) > 2 {
                port = os.Args[2]
        }

        addr := net.JoinHostPort(hostname, port)

        log.Printf("listen: [%s]:%s", hostname, port)

        mux := http.NewServeMux()

        mux.HandleFunc("/hivemind/", func(w http.ResponseWriter, r *http.Request) {
                log.Println("url.pathname:", r.URL.Path)

                body := []byte(`{
                        "jsonrpc": "2.0",
                        "id": 0,
                        "method": "hive.db_head_state",
                        "params": {}
                }`)

                req, err := http.NewRequest("POST", "http://localhost:8888", bytes.NewBuffer(body))
                if err != nil {
                        http.Error(w, err.Error(), 500)
                        return
                }

                req.Header.Set("Content-Type", "application/json")
                req.Header.Set("Host", "steememory.com")

                client := &http.Client{}

                resp, err := client.Do(req)
                if err != nil {
                        http.Error(w, err.Error(), 500)
                        return
                }
                defer resp.Body.Close()

                respBody, _ := ioutil.ReadAll(resp.Body)

                w.Header().Set("Content-Type", "application/json")
                w.WriteHeader(resp.StatusCode)
                w.Write(respBody)
        })

        server := &http.Server{
                Handler: mux,
        }


        ip := net.ParseIP(hostname)
        network := "tcp6"
        if ip != nil && ip.To4() != nil {
                network = "tcp4"
        }
//      ln, err := net.Listen("tcp", addr)
        log.Printf("addr: %s %s %s", hostname, network, addr)
        ln, err := net.Listen(network, addr)


        if err != nil {
                log.Fatal(err)
        }

        log.Fatal(server.Serve(ln))
}

🪣IPv4で待ち受ける
image.png

🪣IPv6で待ち受ける
成功する。😁
image.png

📌0.0.0.0でオープンした場合
tcp4のみオープン。
image.png
📌::でオープンした場合
tcp6のみオープン。😁
image.png