Skip to content

Commit c6cae88

Browse files
committed
Merge branch 'main' into repo
* main: Use patched webtransport-go Upgrade dependencies face: introduce HTTP/3 WebTransport listener build(deps): bump golang.org/x/crypto
2 parents 537d6cb + 19a8a94 commit c6cae88

File tree

9 files changed

+408
-73
lines changed

9 files changed

+408
-73
lines changed

fw/cmd/yanfd.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ type YaNFD struct {
2424

2525
unixListener *face.UnixStreamListener
2626
wsListener *face.WebSocketListener
27+
h3Listener *face.HTTP3Listener
2728
tcpListeners []*face.TCPListener
2829
udpListeners []*face.UDPListener
2930
}
@@ -211,6 +212,26 @@ func (y *YaNFD) Start() {
211212
}
212213
}
213214

215+
// Set up HTTP/3 WebTransport listener
216+
if c := core.C.Faces.HTTP3; c.Enabled {
217+
cfg := face.HTTP3ListenerConfig{
218+
Bind: c.Bind,
219+
Port: c.Port,
220+
TLSCert: c.TlsCert,
221+
TLSKey: c.TlsKey,
222+
}
223+
224+
h3Listener, err := face.NewHTTP3Listener(cfg)
225+
if err != nil {
226+
core.Log.Error(y, "Unable to create HTTP/3 WebTransport Listener", "cfg", cfg, "err", err)
227+
} else {
228+
listenerCount++
229+
go h3Listener.Run()
230+
y.h3Listener = h3Listener
231+
core.Log.Info(y, "Created HTTP/3 WebTransport listener", "uri", cfg.URL().String())
232+
}
233+
}
234+
214235
// Check if any faces were created
215236
if listenerCount <= 0 {
216237
core.Log.Fatal(y, "No face or listener is successfully created. Quit.")

fw/core/config.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,19 @@ type Config struct {
9292
// TLS private key (relative to the config file)
9393
TlsKey string `json:"tls_key"`
9494
} `json:"websocket"`
95+
96+
HTTP3 struct {
97+
// Whether to enable HTTP/3 WebTransport listener
98+
Enabled bool `json:"enabled"`
99+
// Bind address for HTTP/3 WebTransport listener
100+
Bind string `json:"bind"`
101+
// Port for HTTP/3 WebTransport listener
102+
Port uint16 `json:"port"`
103+
// TLS certificate path (relative to the config file)
104+
TlsCert string `json:"tls_cert"`
105+
// TLS private key (relative to the config file)
106+
TlsKey string `json:"tls_key"`
107+
} `json:"http3"`
95108
} `json:"faces"`
96109

97110
Fw struct {
@@ -189,6 +202,12 @@ func DefaultConfig() *Config {
189202
c.Faces.WebSocket.TlsCert = ""
190203
c.Faces.WebSocket.TlsKey = ""
191204

205+
c.Faces.HTTP3.Enabled = false
206+
c.Faces.HTTP3.Bind = ""
207+
c.Faces.HTTP3.Port = 443
208+
c.Faces.HTTP3.TlsCert = ""
209+
c.Faces.HTTP3.TlsKey = ""
210+
192211
c.Fw.Threads = 8
193212
c.Fw.QueueSize = 1024
194213
c.Fw.LockThreadsToCores = false

fw/defn/uri.go

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ package defn
1010
import (
1111
"errors"
1212
"net"
13+
"net/netip"
1314
"net/url"
1415
"os"
1516
"regexp"
@@ -38,6 +39,7 @@ const (
3839
unixURI
3940
wsURI
4041
wsclientURI
42+
quicURI
4143
)
4244

4345
// URI represents a URI for a face.
@@ -147,6 +149,16 @@ func MakeWebSocketClientFaceURI(addr net.Addr) *URI {
147149
}
148150
}
149151

152+
// MakeQuicFaceURI constructs a URI for an HTTP/3 WebTransport endpoint.
153+
func MakeQuicFaceURI(addr netip.AddrPort) *URI {
154+
return &URI{
155+
uriType: quicURI,
156+
scheme: "quic",
157+
path: addr.Addr().String(),
158+
port: addr.Port(),
159+
}
160+
}
161+
150162
func DecodeURIString(str string) *URI {
151163
ret := &URI{
152164
uriType: unknownURI,
@@ -167,6 +179,23 @@ func DecodeURIString(str string) *URI {
167179
return ret
168180
}
169181

182+
decodeHostPort := func(uriType URIType, defaultPort uint16) {
183+
ret.uriType = uriType
184+
185+
ret.scheme = uri.Scheme
186+
ret.path = uri.Hostname()
187+
if uri.Port() != "" {
188+
port, _ := strconv.ParseUint(uri.Port(), 10, 16)
189+
ret.port = uint16(port)
190+
} else {
191+
ret.port = uint16(6363) // default NDN port
192+
}
193+
194+
if zone != "" {
195+
ret.path += "%" + zone
196+
}
197+
}
198+
170199
switch uri.Scheme {
171200
case "dev":
172201
ret.uriType = devURI
@@ -182,26 +211,12 @@ func DecodeURIString(str string) *URI {
182211
case "null":
183212
ret.uriType = nullURI
184213
ret.scheme = uri.Scheme
185-
case "udp", "udp4", "udp6", "tcp", "tcp4", "tcp6":
186-
if strings.HasPrefix(uri.Scheme, "udp") {
187-
ret.uriType = udpURI
188-
} else {
189-
ret.uriType = tcpURI
190-
}
191-
192-
ret.scheme = uri.Scheme
193-
ret.path = uri.Hostname()
194-
if uri.Port() != "" {
195-
port, _ := strconv.ParseUint(uri.Port(), 10, 16)
196-
ret.port = uint16(port)
197-
} else {
198-
ret.port = uint16(6363) // default NDN port
199-
}
200-
201-
if zone != "" {
202-
ret.path += "%" + zone
203-
}
204-
214+
case "udp", "udp4", "udp6":
215+
decodeHostPort(udpURI, 6363)
216+
case "tcp", "tcp4", "tcp6":
217+
decodeHostPort(tcpURI, 6363)
218+
case "quic":
219+
decodeHostPort(quicURI, 443)
205220
case "unix":
206221
ret.uriType = unixURI
207222
ret.scheme = uri.Scheme
@@ -401,7 +416,7 @@ func (u *URI) String() string {
401416
return "internal://"
402417
case nullURI:
403418
return "null://"
404-
case udpURI, tcpURI, wsURI, wsclientURI:
419+
case udpURI, tcpURI, wsURI, wsclientURI, quicURI:
405420
return u.scheme + "://" + net.JoinHostPort(u.path, strconv.FormatUint(uint64(u.port), 10))
406421
case unixURI:
407422
return u.scheme + "://" + u.path

fw/defn/uri_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,12 @@ func TestDecodeUri(t *testing.T) {
8484
assert.Equal(t, "127.0.0.1", uri.PathHost())
8585
assert.Equal(t, uint16(800), uri.Port())
8686

87+
// QUIC URI
88+
uri = defn.DecodeURIString("quic://[::1]:443")
89+
assert.False(t, uri.IsCanonical())
90+
assert.Equal(t, "quic", uri.Scheme())
91+
assert.Equal(t, uint16(443), uri.Port())
92+
8793
// UDP4 with zone
8894
uri = defn.DecodeURIString("udp://127.0.0.1%eth0:3000")
8995
assert.True(t, uri.IsCanonical())

fw/face/http3-listener.go

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
//go:build !tinygo
2+
3+
package face
4+
5+
import (
6+
"crypto/tls"
7+
"errors"
8+
"fmt"
9+
"net"
10+
"net/http"
11+
"net/netip"
12+
"net/url"
13+
"strconv"
14+
"time"
15+
16+
"github.com/named-data/ndnd/fw/core"
17+
"github.com/quic-go/quic-go"
18+
"github.com/quic-go/quic-go/http3"
19+
"github.com/quic-go/webtransport-go"
20+
)
21+
22+
// HTTP3ListenerConfig contains HTTP/3 WebTransport listener configuration.
23+
type HTTP3ListenerConfig struct {
24+
Bind string
25+
Port uint16
26+
TLSCert string
27+
TLSKey string
28+
}
29+
30+
func (cfg HTTP3ListenerConfig) addr() string {
31+
return net.JoinHostPort(cfg.Bind, strconv.FormatUint(uint64(cfg.Port), 10))
32+
}
33+
34+
func (cfg HTTP3ListenerConfig) URL() *url.URL {
35+
u := &url.URL{
36+
Scheme: "https",
37+
Host: cfg.addr(),
38+
}
39+
return u
40+
}
41+
42+
func (cfg HTTP3ListenerConfig) String() string {
43+
return fmt.Sprintf("http3-listener (url=%s)", cfg.URL())
44+
}
45+
46+
// HTTP3Listener listens for incoming HTTP/3 WebTransport sessions.
47+
type HTTP3Listener struct {
48+
mux *http.ServeMux
49+
server *webtransport.Server
50+
}
51+
52+
func NewHTTP3Listener(cfg HTTP3ListenerConfig) (*HTTP3Listener, error) {
53+
l := &HTTP3Listener{}
54+
55+
cert, e := tls.LoadX509KeyPair(cfg.TLSCert, cfg.TLSKey)
56+
if e != nil {
57+
return nil, fmt.Errorf("tls.LoadX509KeyPair(%s %s): %w", cfg.TLSCert, cfg.TLSKey, e)
58+
}
59+
60+
l.mux = http.NewServeMux()
61+
l.mux.HandleFunc("/ndn", l.handler)
62+
63+
l.server = &webtransport.Server{
64+
H3: http3.Server{
65+
Addr: cfg.addr(),
66+
TLSConfig: &tls.Config{
67+
Certificates: []tls.Certificate{cert},
68+
MinVersion: tls.VersionTLS12,
69+
},
70+
QUICConfig: &quic.Config{
71+
MaxIdleTimeout: 60 * time.Second,
72+
KeepAlivePeriod: 30 * time.Second,
73+
DisablePathMTUDiscovery: true,
74+
},
75+
Handler: l.mux,
76+
},
77+
CheckOrigin: func(r *http.Request) bool {
78+
return true
79+
},
80+
}
81+
82+
return l, nil
83+
}
84+
85+
func (l *HTTP3Listener) String() string {
86+
return "HTTP/3 listener"
87+
}
88+
89+
func (l *HTTP3Listener) Run() {
90+
e := l.server.ListenAndServe()
91+
if !errors.Is(e, http.ErrServerClosed) {
92+
core.Log.Fatal(l, "Unable to start listener", "err", e)
93+
}
94+
}
95+
96+
func (l *HTTP3Listener) handler(rw http.ResponseWriter, r *http.Request) {
97+
c, e := l.server.Upgrade(rw, r)
98+
if e != nil {
99+
return
100+
}
101+
102+
remote, e := netip.ParseAddrPort(r.RemoteAddr)
103+
if e != nil {
104+
return
105+
}
106+
local, e := netip.ParseAddrPort(r.Context().Value(http.LocalAddrContextKey).(net.Addr).String())
107+
if e != nil {
108+
return
109+
}
110+
111+
newTransport := NewHTTP3Transport(remote, local, c)
112+
core.Log.Info(l, "Accepting new HTTP/3 WebTransport face", "remote", r.RemoteAddr)
113+
114+
options := MakeNDNLPLinkServiceOptions()
115+
options.IsFragmentationEnabled = true
116+
MakeNDNLPLinkService(newTransport, options).Run(nil)
117+
}

fw/face/http3-transport.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
//go:build !tinygo
2+
3+
package face
4+
5+
import (
6+
"fmt"
7+
"net/netip"
8+
9+
"github.com/named-data/ndnd/fw/core"
10+
defn "github.com/named-data/ndnd/fw/defn"
11+
spec_mgmt "github.com/named-data/ndnd/std/ndn/mgmt_2022"
12+
"github.com/quic-go/webtransport-go"
13+
)
14+
15+
type HTTP3Transport struct {
16+
transportBase
17+
c *webtransport.Session
18+
}
19+
20+
func NewHTTP3Transport(remote, local netip.AddrPort, c *webtransport.Session) (t *HTTP3Transport) {
21+
t = &HTTP3Transport{c: c}
22+
t.makeTransportBase(defn.MakeQuicFaceURI(remote), defn.MakeQuicFaceURI(local), spec_mgmt.PersistencyOnDemand, defn.NonLocal, defn.PointToPoint, 1000)
23+
t.running.Store(true)
24+
return
25+
}
26+
27+
func (t *HTTP3Transport) String() string {
28+
return fmt.Sprintf("http3-transport (faceid=%d remote=%s local=%s)", t.faceID, t.remoteURI, t.localURI)
29+
}
30+
31+
func (t *HTTP3Transport) SetPersistency(persistency spec_mgmt.Persistency) bool {
32+
return persistency == spec_mgmt.PersistencyOnDemand
33+
}
34+
35+
func (t *HTTP3Transport) GetSendQueueSize() uint64 {
36+
return 0
37+
}
38+
39+
func (t *HTTP3Transport) sendFrame(frame []byte) {
40+
if !t.running.Load() {
41+
return
42+
}
43+
44+
if len(frame) > t.MTU() {
45+
core.Log.Warn(t, "Attempted to send frame larger than MTU")
46+
return
47+
}
48+
49+
e := t.c.SendDatagram(frame)
50+
if e != nil {
51+
core.Log.Warn(t, "Unable to send on socket - Face DOWN", "err", e)
52+
t.Close()
53+
return
54+
}
55+
56+
t.nOutBytes += uint64(len(frame))
57+
}
58+
59+
func (t *HTTP3Transport) runReceive() {
60+
defer t.Close()
61+
62+
for {
63+
message, err := t.c.ReceiveDatagram(t.c.Context())
64+
if err != nil {
65+
core.Log.Warn(t, "Unable to read from WebTransport - DROP and Face DOWN", "err", err)
66+
return
67+
}
68+
69+
if len(message) > defn.MaxNDNPacketSize {
70+
core.Log.Warn(t, "Received too much data without valid TLV block")
71+
continue
72+
}
73+
74+
t.nInBytes += uint64(len(message))
75+
t.linkService.handleIncomingFrame(message)
76+
}
77+
}
78+
79+
func (t *HTTP3Transport) Close() {
80+
t.running.Store(false)
81+
t.c.CloseWithError(0, "")
82+
}

fw/yanfd.sample.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,18 @@ faces:
6363
# TLS private key (relative to the config file)
6464
tls_key: ""
6565

66+
http3:
67+
# Whether to enable HTTP/3 WebTransport listener
68+
enabled: false
69+
# Bind address for HTTP/3 WebTransport listener
70+
bind: ""
71+
# Port for HTTP/3 WebTransport listener
72+
port: 443
73+
# TLS certificate path (relative to the config file)
74+
tls_cert: ""
75+
# TLS private key (relative to the config file)
76+
tls_key: ""
77+
6678
fw:
6779
# Number of forwarding threads
6880
threads: 8

0 commit comments

Comments
 (0)