コンテンツにスキップ

Origin ヘッダーの検査

ブラウザーは、WebTransport の Extended CONNECT(セッションを開くための CONNECT リクエスト)のたびに、そのページの Origin を送ってきます。 このサーバーは 1 つの Origin だけを許可し、それ以外はセッションを確立する前に 403 で拒否します。

コード

main.c
#define WIRED_MAIN
#include "wired.h"
/* draft-ietf-webtrans-http3-15 SS3.1 / RFC 6454: an explicit allow-list.
* A CONNECT with no Origin header at all arrives here as an empty span --
* the SDK itself (srvrun.c's wt_origin_ok) already answers 403 for a
* present-but-empty Origin header before this callback ever runs, so an
* empty span here always means "absent", never "present but empty". */
static int allowed_origin(void* ctx, wired_span origin, wired_span authority) {
(void)ctx;
(void)authority;
return wired_span_eq_cstr(origin, "https://ok.example");
}
int wired_main(int argc, char** argv) {
/* The same fixed demo identity as the HTTP/3 hello page. */
static wired_srvboot_demo_keys keys;
wired_srvboot_id id;
wired_srvboot_demo(&id, &keys, 0x50, "guide-wo");
id.max_datagram_frame_size = 65535; /* WebTransport requires DATAGRAM */
wired_srvrun_opt opt = {0};
opt.incoming_cpu = -1;
opt.wt_protocols = "chat";
opt.wt_origin_check = allowed_origin;
u16 port = (u16)wired_cliargs_int(argc, argv, "--port", 4433);
wired_srvrun_handler h = {0};
wired_srvrun_obs obs = {0};
return wired_server_run_opt(port, &id, h, obs, &opt) ? 0 : 1;
}

クライアント

client.go
package main
import (
"context"
"crypto/tls"
"fmt"
"net/http"
"os"
"github.com/quic-go/quic-go"
"github.com/quic-go/webtransport-go"
)
// dial sends one Extended CONNECT with the given Origin header and prints
// its status, closing whatever connection resulted before returning.
func dial(addr, origin string) {
ctx := context.Background()
var conn *quic.Conn
d := webtransport.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
DialAddr: func(ctx context.Context, addr string, t *tls.Config, q *quic.Config) (*quic.Conn, error) {
c, err := quic.DialAddrEarly(ctx, addr, t, q)
conn = c
return c, err
},
}
hdr := http.Header{}
hdr.Set("Origin", origin)
rsp, _, err := d.Dial(ctx, addr+"/chat", hdr)
if rsp == nil {
panic(err)
}
fmt.Println(origin, "status", rsp.StatusCode)
// Closing the whole connection (not the session separately) keeps the
// server's teardown log a single, deterministic "conn closed by peer"
// line per dial.
conn.CloseWithError(0, "")
}
func main() {
addr := os.Args[1]
dial(addr, "https://ok.example")
dial(addr, "https://evil.example")
}

実行結果

Terminal window
$ ./wt-origin --port 4433
conn closed by peer (CONNECTION_CLOSE)
conn closed by peer (CONNECTION_CLOSE)
Terminal window
$ go run . https://127.0.0.1:4433
https://ok.example status 200
https://evil.example status 403
  • opt.wt_origin_check は、wt_resource_check(登録している場合)が受理した後、セッションを確立する前に呼ばれます。 0 を返すと、セッションを作らずに 403 を返します。 既定値(0、未設定)では、形式の正しい Origin をすべて受理します。
  • origin は、CONNECT の Origin ヘッダーをビュー(コピーではなく元のバッファーへの参照)として渡したもので、呼び出しのあいだだけ有効です。 明示的に空の Origin: を送った CONNECT は、このコールバックに届きません。 wt_origin_check が動く前に、SDK 自身が 403 を返します。 空の span としてこのコールバックに届くのは、Origin ヘッダーをまったく送らなかった CONNECT だけです。
  • authority(CONNECT の :authority、リクエスト先のホスト名)も渡されるので、仮想ホストごとに判定を変えることもできます。 このページでは使っていません。
  • 以下の 2 回の呼び出しは、どちらも同じ /chat というパスを指定しています。 違うのは Origin ヘッダーだけです。