-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(netsim): implement routing (#18)
- Loading branch information
1 parent
7fb5a7d
commit 52508f3
Showing
6 changed files
with
243 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
// SPDX-License-Identifier: GPL-3.0-or-later | ||
|
||
package netsim_test | ||
|
||
import ( | ||
"context" | ||
"crypto/tls" | ||
"fmt" | ||
"io" | ||
"log" | ||
"net" | ||
"net/http" | ||
"net/netip" | ||
"time" | ||
|
||
"github.com/rbmk-project/x/connpool" | ||
"github.com/rbmk-project/x/netsim" | ||
"github.com/rbmk-project/x/netsim/router" | ||
"github.com/rbmk-project/x/netsim/simpki" | ||
) | ||
|
||
// This example shows how to use a router to simulate a network | ||
// topology consisting of a client and multiple servers. | ||
func Example_router() { | ||
// Create a pool to close resources when done. | ||
cpool := connpool.New() | ||
defer cpool.Close() | ||
|
||
// Create the server stack. | ||
serverAddr := netip.MustParseAddr("8.8.8.8") | ||
serverStack := netsim.NewStack(serverAddr) | ||
cpool.Add(serverStack) | ||
|
||
// Create the client stack. | ||
clientAddr := netip.MustParseAddr("130.192.91.211") | ||
clientStack := netsim.NewStack(clientAddr) | ||
cpool.Add(clientStack) | ||
|
||
// Create and configure router | ||
r := router.New() | ||
r.Attach(clientStack) | ||
r.Attach(serverStack) | ||
r.AddRoute(clientStack) | ||
r.AddRoute(serverStack) | ||
|
||
// Create a context with a watchdog timeout. | ||
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) | ||
defer cancel() | ||
|
||
// Create a PKI for the server and obtain the certificate. | ||
pki := simpki.MustNewPKI("testdata") | ||
serverCert := pki.MustNewCert(&simpki.PKICertConfig{ | ||
CommonName: "dns.google", | ||
DNSNames: []string{ | ||
"dns.google.com", | ||
"dns.google", | ||
}, | ||
IPAddrs: []net.IP{ | ||
net.IPv4(8, 8, 8, 8), | ||
net.IPv4(8, 8, 4, 4), | ||
}, | ||
}) | ||
|
||
// Create the HTTP server. | ||
serverEndpoint := netip.AddrPortFrom(serverAddr, 443) | ||
listener, err := serverStack.Listen(ctx, "tcp", serverEndpoint.String()) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
cpool.Add(listener) | ||
serverHTTP := &http.Server{ | ||
Handler: http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { | ||
rw.Write([]byte("Bonsoir, Elliot!\n")) | ||
}), | ||
TLSConfig: &tls.Config{ | ||
Certificates: []tls.Certificate{serverCert}, | ||
}, | ||
} | ||
go serverHTTP.ServeTLS(listener, "", "") | ||
cpool.Add(serverHTTP) | ||
|
||
// Create the HTTP client | ||
clientTxp := &http.Transport{ | ||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { | ||
conn, err := clientStack.DialContext(ctx, "tcp", addr) | ||
if err != nil { | ||
return nil, err | ||
} | ||
cpool.Add(conn) | ||
return conn, nil | ||
}, | ||
TLSClientConfig: &tls.Config{ | ||
RootCAs: pki.CertPool(), | ||
}, | ||
} | ||
clientHTTP := &http.Client{Transport: clientTxp} | ||
|
||
// Get the response body. | ||
resp, err := clientHTTP.Get("https://8.8.8.8/") | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
if resp.StatusCode != http.StatusOK { | ||
log.Fatalf("HTTP request failed: %d", resp.StatusCode) | ||
} | ||
cpool.Add(resp.Body) | ||
body, err := io.ReadAll(resp.Body) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
// Print the response body | ||
fmt.Printf("%s", string(body)) | ||
|
||
// Explicitly close the connections | ||
cpool.Close() | ||
|
||
// Output: | ||
// Bonsoir, Elliot! | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
// SPDX-License-Identifier: GPL-3.0-or-later | ||
|
||
// Package router provides network routing capabilities for testing. | ||
package router | ||
|
||
import ( | ||
"errors" | ||
"net/netip" | ||
|
||
"github.com/rbmk-project/x/netsim/packet" | ||
) | ||
|
||
// Router provides routing capabilities. | ||
type Router struct { | ||
// devs tracks attached [packet.NetworkDevice]. | ||
devs []packet.NetworkDevice | ||
|
||
// srt is the static routing table. | ||
srt map[netip.Addr]packet.NetworkDevice | ||
} | ||
|
||
// New creates a new [*Router]. | ||
func New() *Router { | ||
return &Router{ | ||
devs: make([]packet.NetworkDevice, 0), | ||
srt: make(map[netip.Addr]packet.NetworkDevice), | ||
} | ||
} | ||
|
||
// Attach attaches a [packet.NetworkDevice] to the [*Router]. | ||
func (r *Router) Attach(dev packet.NetworkDevice) { | ||
r.devs = append(r.devs, dev) | ||
go r.readLoop(dev) | ||
} | ||
|
||
// AddRoute adds routes for all addresses of the given [packet.NetworkDevice]. | ||
func (r *Router) AddRoute(dev packet.NetworkDevice) { | ||
for _, addr := range dev.Addresses() { | ||
r.srt[addr] = dev | ||
} | ||
} | ||
|
||
// readLoop reads packets from a [packet.NetworkDevice] until EOF. | ||
func (r *Router) readLoop(dev packet.NetworkDevice) { | ||
for { | ||
select { | ||
case <-dev.EOF(): | ||
return | ||
case pkt := <-dev.Output(): | ||
r.route(pkt) | ||
} | ||
} | ||
} | ||
|
||
var ( | ||
// errTTLExceeded is returned when a packet's TTL is exceeded. | ||
errTTLExceeded = errors.New("TTL exceeded in transit") | ||
|
||
// errNoRouteToHost is returned when there is no route to the host. | ||
errNoRouteToHost = errors.New("no route to host") | ||
|
||
// errBufferFull is returned when the buffer is full. | ||
errBufferFull = errors.New("buffer full") | ||
) | ||
|
||
// route routes a given packet to its destination. | ||
func (r *Router) route(pkt *packet.Packet) error { | ||
// Decrement TTL. | ||
if pkt.TTL <= 0 { | ||
return errTTLExceeded | ||
} | ||
pkt.TTL-- | ||
|
||
// Find next hop. | ||
nextHop := r.srt[pkt.DstAddr] | ||
if nextHop == nil { | ||
return errNoRouteToHost | ||
} | ||
|
||
// Forward packet (non-blocking) | ||
select { | ||
case nextHop.Input() <- pkt: | ||
return nil | ||
default: | ||
return errBufferFull | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters