Origin ヘッダーの検査
ブラウザーは、WebTransport の Extended CONNECT(セッションを開くための CONNECT リクエスト)のたびに、そのページの Origin を送ってきます。
このサーバーは 1 つの Origin だけを許可し、それ以外はセッションを確立する前に 403 で拒否します。
コード
#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;}クライアント
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")}実行結果
$ ./wt-origin --port 4433conn closed by peer (CONNECTION_CLOSE)conn closed by peer (CONNECTION_CLOSE)$ go run . https://127.0.0.1:4433https://ok.example status 200https://evil.example status 403opt.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 ヘッダーだけです。