├── .gitignore ├── Dockerfile ├── LICENSE ├── examples ├── cwlink_bridge │ └── main.go ├── cwlink_client │ └── main.go ├── readme.md ├── taglink │ └── main.go └── template.yaml ├── lambda ├── .gitignore ├── bootstrap.go ├── event.json ├── overlay │ └── network_overlay.go ├── readme.md ├── runtime │ └── client.go ├── template.yaml └── utils │ └── utils.go ├── link └── aws │ ├── cloudwatch │ ├── cloudwatch.go │ ├── cloudwatch_bridge.go │ ├── cloudwatch_link_address.go │ ├── log_link.go │ ├── read_poller.go │ └── write_poller.go │ └── tag │ ├── buffy.go │ ├── buffy_test.go │ ├── ring.go │ ├── ring_test.go │ ├── tag.go │ ├── tag_link.go │ └── tag_link_test.go ├── readme.md ├── start-server.sh └── utils ├── utils.go └── utils_tun.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | 3 | .DS_Store 4 | *.[56789ao] 5 | *.a[56789o] 6 | *.so 7 | *.pyc 8 | ._* 9 | .nfs.* 10 | [56789a].out 11 | *~ 12 | *.orig 13 | *.rej 14 | *.exe 15 | .*.swp 16 | core 17 | *.cgo*.go 18 | *.cgo*.c 19 | _cgo_* 20 | _obj 21 | _test 22 | _testmain.go 23 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM quay.io/deis/go-dev:latest 2 | 3 | ENV DEBIAN_FRONTEND noninteractive 4 | ENV CGO_ENABLED 0 5 | 6 | RUN apt-get update 7 | RUN apt-get install -y iproute2 8 | RUN apt-get install -y telnet 9 | RUN apt-get install -y curl 10 | RUN apt-get install -y iputils-ping 11 | RUN apt-get install -y inetutils-traceroute 12 | RUN apt-get install -y socat 13 | RUN apt-get install -y netcat-openbsd 14 | 15 | RUN go get -u github.com/google/btree 16 | RUN go get -u golang.org/x/sys/unix 17 | 18 | RUN go get -u github.com/google/netstack/tcpip 19 | 20 | RUN go get -u github.com/aws/aws-sdk-go/aws 21 | RUN go get -u github.com/aws/aws-sdk-go/service/lambda 22 | RUN go get github.com/derekparker/delve/cmd/dlv 23 | 24 | EXPOSE 40000 25 | 26 | RUN mkdir -p /go/src/github.com/smithclay/rlinklayer 27 | WORKDIR /go/src/github.com/smithclay/rlinklayer 28 | 29 | COPY . /go/src/github.com/smithclay/rlinklayer 30 | 31 | RUN go build -gcflags "all=-N -l" -o server examples/cwlink_bridge/main.go 32 | RUN go build -gcflags "all=-N -l" -o client examples/cwlink_client/main.go 33 | 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Clay Smith 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. 4 | 5 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -------------------------------------------------------------------------------- /examples/cwlink_bridge/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Clay Smith 2 | 3 | // +build linux 4 | 5 | package main 6 | 7 | import ( 8 | "flag" 9 | "fmt" 10 | "log" 11 | "net" 12 | "os" 13 | "os/signal" 14 | "syscall" 15 | 16 | "github.com/google/netstack/tcpip" 17 | "github.com/google/netstack/tcpip/link/sniffer" 18 | "github.com/google/netstack/tcpip/network/arp" 19 | "github.com/google/netstack/tcpip/network/ipv4" 20 | "github.com/google/netstack/tcpip/stack" 21 | "github.com/google/netstack/tcpip/transport/tcp" 22 | linkaws "github.com/smithclay/rlinklayer/link/aws/cloudwatch" 23 | "github.com/smithclay/rlinklayer/utils" 24 | ) 25 | 26 | var tap = flag.Bool("tap", false, "use tap instead of tun") 27 | var mac = flag.String("mac", "\x74\x74\x74\x74\x74\x74", "mac address to use in tap device") 28 | 29 | func main() { 30 | flag.Parse() 31 | 32 | _, err := newStack() 33 | if err != nil { 34 | log.Fatalf("newStack: could not create: %v", err) 35 | } 36 | 37 | fmt.Println("Press CTRL-C to exit.") 38 | sigs := make(chan os.Signal, 1) 39 | done := make(chan bool, 1) 40 | signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) 41 | go func() { 42 | sig := <-sigs 43 | fmt.Println() 44 | fmt.Println(sig) 45 | done <- true 46 | }() 47 | <-done 48 | fmt.Println("exiting") 49 | } 50 | 51 | func newStack() (*stack.Stack, *tcpip.Error) { 52 | s := stack.New([]string{ipv4.ProtocolName, arp.ProtocolName}, []string{tcp.ProtocolName}, stack.Options{}) 53 | 54 | // AWS network stack 55 | var linkID tcpip.LinkEndpointID 56 | ethernetEnabled := false 57 | pointToPoint := true 58 | var remoteAddress tcpip.LinkAddress 59 | localLink := tcpip.LinkAddress(*mac) 60 | 61 | if *tap { 62 | log.Printf("newStack: Creating tap link endpoint ...") 63 | linkID = utils.NewTapLink("tap0", tcpip.LinkAddress(*mac)) 64 | ethernetEnabled = true 65 | pointToPoint = false 66 | } else { 67 | log.Printf("newStack: Creating tun link endpoint...") 68 | linkID = utils.NewTunLink("tun0") 69 | remoteAddress = tcpip.LinkAddress("\x11\x22\x33\x44\x55\x66") 70 | } 71 | 72 | sniffedTunTap := sniffer.New(linkID) 73 | 74 | log.Printf("newStack: Creating New Cloudwatch Endpoint: Local: %v, Remote: %v", localLink.String(), remoteAddress) 75 | opts := &linkaws.Options{ 76 | NetworkName: "TestNet", 77 | EthernetHeader: ethernetEnabled, 78 | PointToPoint: pointToPoint, 79 | Address: localLink, 80 | LinkEndpoint: sniffedTunTap, 81 | RemoteAddress: remoteAddress, 82 | } 83 | awsLinkID, _ := linkaws.NewBridge(opts) 84 | 85 | if err := s.CreateNIC(1, awsLinkID); err != nil { 86 | log.Fatalf("Could not create NIC card") 87 | } 88 | addr := utils.IpToAddress(net.ParseIP("192.168.1.3")) 89 | if err := s.AddAddress(1, ipv4.ProtocolNumber, addr); err != nil { 90 | log.Fatalf("error %s", err) 91 | } 92 | if err := s.AddAddress(1, arp.ProtocolNumber, arp.ProtocolAddress); err != nil { 93 | log.Fatalf("newStack: Could enable ARP: %s", err) 94 | } 95 | 96 | s.SetRouteTable([]tcpip.Route{{ 97 | Destination: "\x00\x00\x00\x00", 98 | Mask: "\x00\x00\x00\x00", 99 | Gateway: "", 100 | NIC: 1, 101 | }}) 102 | 103 | return s, nil 104 | } 105 | -------------------------------------------------------------------------------- /examples/cwlink_client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/google/netstack/tcpip/network/arp" 6 | "io" 7 | "log" 8 | "net" 9 | "net/http" 10 | "os" 11 | "os/signal" 12 | "strconv" 13 | "strings" 14 | "syscall" 15 | 16 | "github.com/google/netstack/waiter" 17 | 18 | "github.com/google/netstack/tcpip" 19 | "github.com/google/netstack/tcpip/adapters/gonet" 20 | "github.com/google/netstack/tcpip/link/sniffer" 21 | "github.com/google/netstack/tcpip/network/ipv4" 22 | "github.com/google/netstack/tcpip/stack" 23 | "github.com/google/netstack/tcpip/transport/tcp" 24 | linkaws "github.com/smithclay/rlinklayer/link/aws/cloudwatch" 25 | "github.com/smithclay/rlinklayer/utils" 26 | ) 27 | 28 | func listenAndServe(s *stack.Stack, addr tcpip.Address, port int, mux *http.ServeMux) { 29 | fullAddr := tcpip.FullAddress{ 30 | NIC: 1, 31 | Addr: "", 32 | Port: uint16(port), 33 | } 34 | 35 | ln, _ := gonet.NewListener(s, fullAddr, ipv4.ProtocolNumber) 36 | 37 | http.Serve(ln, mux) 38 | } 39 | 40 | func main() { 41 | s := stack.New([]string{ipv4.ProtocolName, arp.ProtocolName}, []string{tcp.ProtocolName}, stack.Options{}) 42 | 43 | opts := &linkaws.Options{ 44 | NetworkName: "TestNet", 45 | EthernetHeader: true, 46 | Address: "\x42\x42\x42\x42\x42\x42", 47 | } 48 | cwLink, _ := linkaws.New(opts) 49 | 50 | sniffed := sniffer.New(cwLink) 51 | if err := s.CreateNIC(1, sniffed); err != nil { 52 | log.Fatalf("Could not create NIC card") 53 | } 54 | 55 | addr := utils.IpToAddress(net.ParseIP("192.168.1.21")) 56 | if err := s.AddAddress(1, ipv4.ProtocolNumber, addr); err != nil { 57 | log.Fatalf("error %s", err) 58 | } 59 | 60 | if err := s.AddAddress(1, arp.ProtocolNumber, arp.ProtocolAddress); err != nil { 61 | log.Fatalf("error: %s", err) 62 | } 63 | 64 | s.SetRouteTable([]tcpip.Route{ 65 | { 66 | Destination: tcpip.Address(strings.Repeat("\x00", 4)), 67 | Mask: tcpip.AddressMask(strings.Repeat("\x00", 4)), 68 | Gateway: "", 69 | NIC: 1, 70 | }, 71 | }) 72 | //s.SetForwarding(true) 73 | 74 | /*mux := http.NewServeMux() 75 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 76 | w.Write([]byte("Hello from netstack over tags!\n")) 77 | }) 78 | go listenAndServe(s, addr, 8080, mux)*/ 79 | 80 | var wq waiter.Queue 81 | fwd := tcp.NewForwarder(s, 0, 10, func(r *tcp.ForwarderRequest) { 82 | ep, er := r.CreateEndpoint(&wq) 83 | if er != nil { 84 | transportEndpointID := r.ID() 85 | log.Println(er, net.JoinHostPort(transportEndpointID.LocalAddress.String(), strconv.Itoa(int(transportEndpointID.LocalPort)))) 86 | r.Complete(false) 87 | return 88 | } 89 | defer ep.Close() 90 | transportEndpointID := r.ID() 91 | r.Complete(false) 92 | log.Printf("NewForwarder Remote: %v:%v", transportEndpointID.RemoteAddress, transportEndpointID.RemotePort) 93 | conn, err := net.Dial("tcp", net.JoinHostPort("0.0.0.0", strconv.Itoa(int(transportEndpointID.LocalPort)))) 94 | if err != nil { 95 | log.Println(err) 96 | return 97 | } 98 | defer conn.Close() 99 | fwdConn := gonet.NewConn(&wq, ep) 100 | go io.Copy(fwdConn, conn) 101 | io.Copy(conn, fwdConn) 102 | }) 103 | s.SetTransportProtocolHandler(tcp.ProtocolNumber, fwd.HandlePacket) 104 | 105 | fmt.Println("Press CTRL-C to exit.") 106 | sigs := make(chan os.Signal, 1) 107 | done := make(chan bool, 1) 108 | signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) 109 | go func() { 110 | sig := <-sigs 111 | fmt.Println() 112 | fmt.Println(sig) 113 | done <- true 114 | }() 115 | <-done 116 | fmt.Println("Exiting...") 117 | } 118 | -------------------------------------------------------------------------------- /examples/readme.md: -------------------------------------------------------------------------------- 1 | ### Examples 2 | 3 | #### Running these examples 4 | 5 | On Mac OS X, you will need to use Docker to run the `cwlink_bridge example` since the `tap` interface is not supported by default in Mac OS X. 6 | 7 | Otherwise, everything can be run as a process. MAC and IP addresses of endpoints are specified in code. 8 | 9 | ```bash 10 | go run examples/cwlink_client/main.go 11 | ``` 12 | 13 | Standard AWS environment variables need to be set to read/write to the appropriate AWS services (Amazon Cloudwatch or AWS Lambda tagS): `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`. 14 | 15 | ### Examples running in AWS Lambda 16 | 17 | There is a [SAM](https://aws.amazon.com/serverless/sam/) template available for deploying a test network to your AWS account. 18 | 19 | ```bash 20 | sam package --s3-bucket [your-bucket] > packaged.yaml 21 | sam deploy --template-file packaged.yaml --stack-name [your-stack] --capabilities CAPABILITY_IAM 22 | ``` 23 | 24 | ### Standalone examples 25 | 26 | These examples can run anywhere, they just need to be able to read/write to specific AWS services. 27 | 28 | ##### cwlink_bridge 29 | 30 | Amazon Cloudwatch-based network stack designed to be bridged with a local tun or tap interface. 31 | 32 | ##### cwlink_client 33 | 34 | Amazon Cloudwatch-based network stack designed to be run in an unpriviliged environment where interfaces cannot be created (i.e. AWS Lambda) 35 | 36 | ##### taglink 37 | 38 | AWS Lambda Tag-based network stack. 39 | 40 | -------------------------------------------------------------------------------- /examples/taglink/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/google/netstack/tcpip" 6 | "github.com/google/netstack/tcpip/network/arp" 7 | "github.com/google/netstack/tcpip/network/ipv4" 8 | "github.com/google/netstack/tcpip/stack" 9 | "github.com/google/netstack/tcpip/transport/tcp" 10 | "github.com/smithclay/rlinklayer/link/aws/tag" 11 | "github.com/smithclay/rlinklayer/utils" 12 | "log" 13 | "net" 14 | "os" 15 | "os/signal" 16 | "syscall" 17 | ) 18 | 19 | func main() { 20 | s := stack.New([]string{ipv4.ProtocolName, arp.ProtocolName}, []string{tcp.ProtocolName}, stack.Options{}) 21 | opts := &tag.Options{ 22 | RemoteArn: "arn:aws:lambda:us-west-2:275197385476:function:helloWorldTestFunction", 23 | LocalArn: "arn:aws:lambda:us-west-2:275197385476:function:helloWorldTestFunction", 24 | RemoteAddress: utils.GenerateRandomMac(), 25 | LocalAddress: utils.GenerateRandomMac(), 26 | } 27 | linkID := tag.New(opts) 28 | if err := s.CreateNIC(1, linkID); err != nil { 29 | log.Fatalf("Could not create NIC card") 30 | } 31 | addr := utils.IpToAddress(net.ParseIP("192.168.1.1")) 32 | if err := s.AddAddress(1, ipv4.ProtocolNumber, addr); err != nil { 33 | log.Fatalf("error %s", err) 34 | } 35 | if err := s.AddAddress(1, arp.ProtocolNumber, arp.ProtocolAddress); err != nil { 36 | log.Fatalf("newStack: Could enable ARP: %s", err) 37 | } 38 | 39 | s.SetRouteTable([]tcpip.Route{{ 40 | Destination: "\x00\x00\x00\x00", 41 | Mask: "\x00\x00\x00\x00", 42 | Gateway: "", 43 | NIC: 1, 44 | }}) 45 | fmt.Println("Press CTRL-C to exit.") 46 | sigs := make(chan os.Signal, 1) 47 | done := make(chan bool, 1) 48 | signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) 49 | go func() { 50 | sig := <-sigs 51 | fmt.Println() 52 | fmt.Println(sig) 53 | done <- true 54 | }() 55 | <-done 56 | fmt.Println("exiting") 57 | } 58 | -------------------------------------------------------------------------------- /examples/template.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Transform: AWS::Serverless-2016-10-31 3 | Description: Test Richard Linklayer Network 4 | 5 | Parameters: 6 | NetworkName: 7 | Type: String 8 | Default: "TestNet" 9 | MacAddress: 10 | Type: String 11 | Default: "\x42\x42\x42\x42\x42\x42" 12 | IpAddress: 13 | Type: String 14 | Default: "192.168.1.21" 15 | 16 | Resources: 17 | # Warning: this uses Cloudwatch Events to run this function... forever. 18 | HttpServer: 19 | Type: AWS::Serverless::Function 20 | Properties: 21 | Handler: default.handler 22 | Runtime: provided 23 | Timeout: 120 24 | ReservedConcurrentExecutions: 1 25 | CodeUri: ./ 26 | Layers: 27 | - arn:aws:lambda:us-west-2:275197385476:layer:richard-linklayer:1 28 | Policies: 29 | - Version: '2012-10-17' 30 | Statement: 31 | - Effect: Allow 32 | Action: 33 | - logs:CreateLogGroup 34 | - logs:CreateLogStream 35 | - logs:FilterLogEvents 36 | - logs:GetLogEvents 37 | - logs:PutLogEvents 38 | Resource: arn:aws:logs:*:*:* 39 | Environment: 40 | Variables: 41 | RUN_CMD: "node -e \"require('http').createServer(function (req, res) { res.end('whoa, dude'); }).listen(3000, '0.0.0.0');\"" 42 | OL_NET_NAME: !Ref NetworkName 43 | OL_MAC_ADDR: !Ref MacAddress 44 | OL_IP_ADDR: !Ref IpAddress 45 | Events: 46 | KeepRunning: 47 | Type: Schedule 48 | Properties: 49 | Schedule: rate(2 minutes) -------------------------------------------------------------------------------- /lambda/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | bootstrap 3 | packaged.yaml 4 | -------------------------------------------------------------------------------- /lambda/bootstrap.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/smithclay/rlinklayer/lambda/overlay" 5 | "github.com/smithclay/rlinklayer/lambda/runtime" 6 | "github.com/smithclay/rlinklayer/lambda/utils" 7 | "log" 8 | "net/http" 9 | "os" 10 | "os/exec" 11 | "time" 12 | ) 13 | 14 | func execProcess() { 15 | handler := os.Getenv("RUN_CMD") 16 | args, err := utils.ParseCommandLine(handler) 17 | if err != nil { 18 | log.Printf("Error: could parse run cmd: '%v'", handler) 19 | os.Exit(1) 20 | } 21 | if len(args) < 1 { 22 | log.Printf("Error: could not get run cmd '%v'", handler) 23 | os.Exit(1) 24 | } 25 | log.Printf("args: %v", args) 26 | cmd := exec.Command(args[0], args[1:]...) 27 | cmd.Stdout = os.Stdout 28 | err = cmd.Run() 29 | if err != nil { 30 | log.Printf("Error: could not run handler: %v", err) 31 | } 32 | log.Printf("Handler exited prematurely.") 33 | os.Exit(2) 34 | } 35 | 36 | func processEvents(rc *runtime.Client) { 37 | for { 38 | requestId, deadline, err := rc.GetInvocation() 39 | if err != nil { 40 | log.Printf("Error: could not get next invocation: %v", err) 41 | err = rc.PostInitializationError(err.Error()) 42 | if err != nil { 43 | os.Exit(1) 44 | } 45 | } 46 | // TODO: configure this 47 | log.Printf("Function deadline in: %v", deadline) 48 | 49 | // Keep accepting connections until 5 seconds before the funtion times out. 50 | remainingTime := time.Duration(deadline-time.Now().Unix()*1000) * time.Millisecond 51 | time.Sleep(remainingTime - (5 * time.Second)) 52 | 53 | err = rc.PostResponse(requestId, "SUCCESS") 54 | } 55 | } 56 | 57 | func startNetwork() { 58 | netName := os.Getenv("OL_NET_NAME") 59 | macAddress := os.Getenv("OL_MAC_ADDR") 60 | ipAddr := os.Getenv("OL_IP_ADDR") 61 | opts := overlay.Options{MacAddress: macAddress, 62 | IP: ipAddr, 63 | NetworkName: netName, 64 | OverlayType: overlay.CloudwatchLog, 65 | } 66 | no := overlay.New(opts) 67 | no.Start() 68 | } 69 | 70 | func main() { 71 | log.SetPrefix("[bootstrap] ") 72 | 73 | runtimeClient := runtime.New(&http.Client{}) 74 | go execProcess() 75 | startNetwork() 76 | processEvents(runtimeClient) 77 | } 78 | -------------------------------------------------------------------------------- /lambda/event.json: -------------------------------------------------------------------------------- 1 | { 2 | "body": "eyJ0ZXN0IjoiYm9keSJ9", 3 | "resource": "/{proxy+}", 4 | "requestContext": { 5 | "requestTime": "09/Apr/2015:12:34:56 +0000", 6 | "protocol": "HTTP/1.1", 7 | "resourceId": "123456", 8 | "apiId": "1234567890", 9 | "resourcePath": "/{proxy+}", 10 | "httpMethod": "POST", 11 | "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", 12 | "path": "/prod/path/to/resource", 13 | "requestTimeEpoch": 1428582896000, 14 | "accountId": "123456789012", 15 | "identity": { 16 | "userArn": null, 17 | "cognitoAuthenticationType": null, 18 | "accessKey": null, 19 | "caller": null, 20 | "userAgent": "Custom User Agent String", 21 | "user": null, 22 | "cognitoIdentityPoolId": null, 23 | "cognitoIdentityId": null, 24 | "cognitoAuthenticationProvider": null, 25 | "sourceIp": "127.0.0.1", 26 | "accountId": null 27 | }, 28 | "stage": "prod" 29 | }, 30 | "queryStringParameters": { 31 | "foo": "bar" 32 | }, 33 | "headers": { 34 | "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", 35 | "Accept-Language": "en-US,en;q=0.8", 36 | "CloudFront-Is-Desktop-Viewer": "true", 37 | "CloudFront-Is-SmartTV-Viewer": "false", 38 | "CloudFront-Is-Mobile-Viewer": "false", 39 | "X-Forwarded-For": "127.0.0.1, 127.0.0.2", 40 | "CloudFront-Viewer-Country": "US", 41 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", 42 | "Upgrade-Insecure-Requests": "1", 43 | "X-Forwarded-Port": "443", 44 | "Host": "1234567890.execute-api.us-east-1.amazonaws.com", 45 | "X-Forwarded-Proto": "https", 46 | "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", 47 | "CloudFront-Is-Tablet-Viewer": "false", 48 | "Cache-Control": "max-age=0", 49 | "User-Agent": "Custom User Agent String", 50 | "CloudFront-Forwarded-Proto": "https", 51 | "Accept-Encoding": "gzip, deflate, sdch" 52 | }, 53 | "pathParameters": { 54 | "proxy": "/path/to/resource" 55 | }, 56 | "httpMethod": "POST", 57 | "stageVariables": { 58 | "baz": "qux" 59 | }, 60 | "path": "/path/to/resource", 61 | "isBase64Encoded": true 62 | } -------------------------------------------------------------------------------- /lambda/overlay/network_overlay.go: -------------------------------------------------------------------------------- 1 | package overlay 2 | 3 | import ( 4 | "github.com/google/netstack/tcpip" 5 | "github.com/google/netstack/tcpip/adapters/gonet" 6 | "github.com/google/netstack/tcpip/link/sniffer" 7 | "github.com/google/netstack/tcpip/network/arp" 8 | "github.com/google/netstack/tcpip/network/ipv4" 9 | "github.com/google/netstack/tcpip/stack" 10 | "github.com/google/netstack/tcpip/transport/tcp" 11 | "github.com/google/netstack/waiter" 12 | cwLink "github.com/smithclay/rlinklayer/link/aws/cloudwatch" 13 | tagLink "github.com/smithclay/rlinklayer/link/aws/tag" 14 | "github.com/smithclay/rlinklayer/utils" 15 | "io" 16 | "log" 17 | "net" 18 | "strconv" 19 | "strings" 20 | ) 21 | 22 | type NetworkType int 23 | 24 | const ( 25 | CloudwatchLog NetworkType = 1 26 | LambdaTag NetworkType = 2 27 | ) 28 | 29 | type NetworkOverlay struct { 30 | netName string 31 | stack *stack.Stack 32 | mac tcpip.LinkAddress 33 | remoteMac tcpip.LinkAddress 34 | netType NetworkType 35 | ip string 36 | // Lambda Tag Specific 37 | localArn string 38 | remoteArn string 39 | } 40 | 41 | type Options struct { 42 | IP string 43 | OverlayType NetworkType 44 | NetworkName string 45 | MacAddress string 46 | RemoteMacAddress string 47 | // Lambda Tag specific 48 | LocalArn string 49 | RemoteArn string 50 | } 51 | 52 | func New(opts Options) *NetworkOverlay { 53 | return &NetworkOverlay{netName: opts.NetworkName, 54 | mac: tcpip.LinkAddress(opts.MacAddress), 55 | ip: opts.IP, 56 | netType: opts.OverlayType} 57 | } 58 | 59 | func (no *NetworkOverlay) Start() { 60 | no.stack = stack.New([]string{ipv4.ProtocolName, arp.ProtocolName}, []string{tcp.ProtocolName}, stack.Options{}) 61 | 62 | var endpointID tcpip.LinkEndpointID 63 | 64 | if no.netType == LambdaTag { 65 | opts := &tagLink.Options{no.localArn, 66 | no.remoteArn, 67 | tcpip.LinkAddress(no.mac), 68 | tcpip.LinkAddress(no.remoteMac), 69 | } 70 | endpointID = tagLink.New(opts) 71 | } 72 | 73 | if no.netType == CloudwatchLog { 74 | opts := &cwLink.Options{ 75 | NetworkName: no.netName, 76 | Address: tcpip.LinkAddress(no.mac), 77 | EthernetHeader: true, 78 | } 79 | endpointID, _ = cwLink.New(opts) 80 | } 81 | 82 | sniffed := sniffer.New(endpointID) 83 | if err := no.stack.CreateNIC(1, sniffed); err != nil { 84 | log.Fatalf("Could not create NIC card") 85 | } 86 | addr := utils.IpToAddress(net.ParseIP(no.ip)) 87 | 88 | if err := no.stack.AddAddress(1, ipv4.ProtocolNumber, addr); err != nil { 89 | log.Fatalf("AddAddress error [ipv4]: %s", err) 90 | } 91 | 92 | if err := no.stack.AddAddress(1, arp.ProtocolNumber, arp.ProtocolAddress); err != nil { 93 | log.Fatalf("AddAddress error [arp]: %s", err) 94 | } 95 | 96 | no.stack.SetRouteTable([]tcpip.Route{ 97 | { 98 | Destination: tcpip.Address(strings.Repeat("\x00", 4)), 99 | Mask: tcpip.AddressMask(strings.Repeat("\x00", 4)), 100 | Gateway: "", 101 | NIC: 1, 102 | }, 103 | }) 104 | no.forwardTCP() 105 | } 106 | 107 | func (no *NetworkOverlay) forwardTCP() { 108 | var wq waiter.Queue 109 | fwd := tcp.NewForwarder(no.stack, 0, 10, func(r *tcp.ForwarderRequest) { 110 | ep, er := r.CreateEndpoint(&wq) 111 | if er != nil { 112 | transportEndpointID := r.ID() 113 | log.Println(er, net.JoinHostPort(transportEndpointID.LocalAddress.String(), strconv.Itoa(int(transportEndpointID.LocalPort)))) 114 | r.Complete(false) 115 | return 116 | } 117 | defer ep.Close() 118 | transportEndpointID := r.ID() 119 | r.Complete(false) 120 | log.Printf("NewForwarder Remote: %v:%v", transportEndpointID.RemoteAddress, transportEndpointID.RemotePort) 121 | conn, err := net.Dial("tcp", net.JoinHostPort("0.0.0.0", strconv.Itoa(int(transportEndpointID.LocalPort)))) 122 | if err != nil { 123 | log.Println(err) 124 | return 125 | } 126 | defer conn.Close() 127 | fwdConn := gonet.NewConn(&wq, ep) 128 | go io.Copy(fwdConn, conn) 129 | io.Copy(conn, fwdConn) 130 | }) 131 | no.stack.SetTransportProtocolHandler(tcp.ProtocolNumber, fwd.HandlePacket) 132 | } 133 | -------------------------------------------------------------------------------- /lambda/readme.md: -------------------------------------------------------------------------------- 1 | ### Custom AWS Lambda Runtime 2 | 3 | #### Running 4 | 5 | ```bash 6 | $ GOOS=linux GOARCH=amd64 go build -o bootstrap ./bootstrap.go 7 | $ sam local invoke -e event.json 8 | ``` 9 | 10 | #### Deploying 11 | 12 | ```bash 13 | $ sam package \ 14 | --template-file template.yaml \ 15 | --output-template-file packaged.yaml \ 16 | --s3-bucket rlinklayer-test-bckt 17 | ``` -------------------------------------------------------------------------------- /lambda/runtime/client.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "net/http" 7 | "net/url" 8 | "os" 9 | "strconv" 10 | ) 11 | 12 | type Client struct { 13 | BaseURL *url.URL 14 | UserAgent string 15 | httpClient *http.Client 16 | } 17 | 18 | const RuntimeInitErrorPath = "/2018-06-01/runtime/init/error" 19 | const NextEventPath = "/2018-06-01/runtime/invocation/next" 20 | const ResponsePath = "/2018-06-01/runtime/invocation/%s/response" 21 | const ResponseErrorPath = "/2018-06-01/runtime/invocation/%s/error" 22 | 23 | const UserAgent = "OverlazyRuntimeGoClient" 24 | 25 | func New(client *http.Client) *Client { 26 | return &Client{BaseURL: &url.URL{Host: os.Getenv("AWS_LAMBDA_RUNTIME_API"), Scheme: "http"}, UserAgent: UserAgent, httpClient: client} 27 | } 28 | 29 | func (c *Client) GetInvocation() (string, int64, error) { 30 | rel := &url.URL{Path: NextEventPath} 31 | u := c.BaseURL.ResolveReference(rel) 32 | req, err := http.NewRequest("GET", u.String(), nil) 33 | if err != nil { 34 | return "", 0, err 35 | } 36 | req.Header.Set("Accept", "application/json") 37 | req.Header.Set("User-Agent", c.UserAgent) 38 | 39 | resp, err := c.do(req) 40 | if err != nil { 41 | return "", 0, err 42 | } 43 | 44 | n, err := strconv.ParseInt(resp.Header.Get("Lambda-Runtime-Deadline-Ms"), 10, 64) 45 | if err != nil { 46 | return "", 0, err 47 | } 48 | 49 | return resp.Header.Get("Lambda-Runtime-Aws-Request-Id"), n, err 50 | } 51 | 52 | func (c *Client) PostResponse(requestId string, body string) error { 53 | rel := &url.URL{Path: fmt.Sprintf(ResponsePath, requestId)} 54 | u := c.BaseURL.ResolveReference(rel) 55 | 56 | req, err := http.NewRequest("POST", u.String(), bytes.NewBuffer([]byte(body))) 57 | req.Header.Set("Content-Type", "text/plain") 58 | _, err = c.do(req) 59 | if err != nil { 60 | return err 61 | } 62 | return nil 63 | } 64 | 65 | func (c *Client) PostError(requestId string, body string) error { 66 | rel := &url.URL{Path: fmt.Sprintf(ResponseErrorPath, requestId)} 67 | u := c.BaseURL.ResolveReference(rel) 68 | 69 | req, err := http.NewRequest("POST", u.String(), bytes.NewBuffer([]byte(body))) 70 | req.Header.Set("Content-Type", "text/plain") 71 | req.Header.Set("Lambda-Runtime-Function-Error-Type", "Unhandled") 72 | 73 | _, err = c.do(req) 74 | if err != nil { 75 | return err 76 | } 77 | return nil 78 | } 79 | 80 | func (c *Client) PostInitializationError(body string) error { 81 | rel := &url.URL{Path: RuntimeInitErrorPath} 82 | u := c.BaseURL.ResolveReference(rel) 83 | 84 | req, err := http.NewRequest("POST", u.String(), bytes.NewBuffer([]byte(body))) 85 | req.Header.Set("Content-Type", "text/plain") 86 | req.Header.Set("Lambda-Runtime-Function-Error-Type", "Unhandled") 87 | 88 | _, err = c.do(req) 89 | if err != nil { 90 | return err 91 | } 92 | return nil 93 | } 94 | 95 | func (c *Client) do(req *http.Request) (*http.Response, error) { 96 | resp, err := c.httpClient.Do(req) 97 | if err != nil { 98 | return nil, err 99 | } 100 | defer resp.Body.Close() 101 | return resp, err 102 | } 103 | -------------------------------------------------------------------------------- /lambda/template.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Transform: AWS::Serverless-2016-10-31 3 | Description: Richard Linklayer Custom Runtime 4 | 5 | Resources: 6 | DeploymentPermission: 7 | Type: "AWS::Lambda::LayerVersionPermission" 8 | Properties: 9 | Action: lambda:GetLayerVersion 10 | LayerVersionArn: !Ref RichardLinklayer 11 | Principal: '*' 12 | 13 | RichardLinklayer: 14 | Type: AWS::Serverless::LayerVersion 15 | Properties: 16 | LayerName: richard-linklayer 17 | Description: Richard Linklayer Custom Runtime 18 | ContentUri: ./bootstrap 19 | CompatibleRuntimes: 20 | - nodejs8.10 21 | LicenseInfo: 'MIT' 22 | RetentionPolicy: Retain 23 | -------------------------------------------------------------------------------- /lambda/utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "fmt" 4 | import "errors" 5 | 6 | // ParseCommandLine splits a string into a slice that can be passed to exec.Command( 7 | func ParseCommandLine(command string) ([]string, error) { 8 | var args []string 9 | state := "start" 10 | current := "" 11 | quote := "\"" 12 | escapeNext := true 13 | for i := 0; i < len(command); i++ { 14 | c := command[i] 15 | 16 | if state == "quotes" { 17 | if string(c) != quote { 18 | current += string(c) 19 | } else { 20 | args = append(args, current) 21 | current = "" 22 | state = "start" 23 | } 24 | continue 25 | } 26 | 27 | if escapeNext { 28 | current += string(c) 29 | escapeNext = false 30 | continue 31 | } 32 | 33 | if c == '\\' { 34 | escapeNext = true 35 | continue 36 | } 37 | 38 | if c == '"' || c == '\'' { 39 | state = "quotes" 40 | quote = string(c) 41 | continue 42 | } 43 | 44 | if state == "arg" { 45 | if c == ' ' || c == '\t' { 46 | args = append(args, current) 47 | current = "" 48 | state = "start" 49 | } else { 50 | current += string(c) 51 | } 52 | continue 53 | } 54 | 55 | if c != ' ' && c != '\t' { 56 | state = "arg" 57 | current += string(c) 58 | } 59 | } 60 | 61 | if state == "quotes" { 62 | return []string{}, errors.New(fmt.Sprintf("Unclosed quote in command line: %s", command)) 63 | } 64 | 65 | if current != "" { 66 | args = append(args, current) 67 | } 68 | 69 | return args, nil 70 | } 71 | -------------------------------------------------------------------------------- /link/aws/cloudwatch/cloudwatch.go: -------------------------------------------------------------------------------- 1 | package cloudwatch 2 | 3 | import ( 4 | "github.com/aws/aws-sdk-go/aws" 5 | "github.com/aws/aws-sdk-go/aws/session" 6 | "github.com/aws/aws-sdk-go/service/cloudwatchlogs" 7 | "github.com/google/netstack/tcpip" 8 | "github.com/google/netstack/tcpip/buffer" 9 | "github.com/google/netstack/tcpip/header" 10 | "github.com/google/netstack/tcpip/stack" 11 | "log" 12 | ) 13 | 14 | // todo: configure this 15 | const MTU = 1024 16 | 17 | type endpoint struct { 18 | dispatcher stack.NetworkDispatcher 19 | laddr tcpip.LinkAddress 20 | raddr tcpip.LinkAddress 21 | netName string 22 | logLink *LogLink 23 | hdrSize int 24 | p2p bool 25 | } 26 | 27 | type Options struct { 28 | Address tcpip.LinkAddress 29 | RemoteAddress tcpip.LinkAddress // for point-to-point configuration 30 | PointToPoint bool 31 | EthernetHeader bool 32 | NetworkName string 33 | LinkEndpoint tcpip.LinkEndpointID 34 | } 35 | 36 | // New creates a new endpoint for transmitting data using Amazon Cloudwathc gorups 37 | func New(opts *Options) (tcpip.LinkEndpointID, *endpoint) { 38 | sess, _ := session.NewSession(&aws.Config{ 39 | Region: aws.String("us-west-2")}, 40 | ) 41 | svc := cloudwatchlogs.New(sess) 42 | 43 | ep := &endpoint{ 44 | laddr: opts.Address, 45 | raddr: opts.RemoteAddress, 46 | netName: opts.NetworkName, 47 | p2p: opts.PointToPoint, 48 | } 49 | 50 | if opts.PointToPoint && opts.RemoteAddress == "" { 51 | log.Fatalf("New: Cannot create point-to-point endpoint without a remote link address.") 52 | } 53 | 54 | ep.hdrSize = 0 55 | if opts.EthernetHeader { 56 | ep.hdrSize = header.EthernetMinimumSize 57 | } 58 | 59 | ep.logLink = NewLogLink(&LogConfig{LogService: svc, Endpoint: ep, NetName: opts.NetworkName}) 60 | 61 | return stack.RegisterLinkEndpoint(ep), ep 62 | } 63 | 64 | // Attach implements the stack.LinkEndpoint interface. It saves the dispatcher 65 | // and registers with the lower endpoint as its dispatcher so that "e" is called 66 | // for inbound packets. 67 | func (e *endpoint) Attach(dispatcher stack.NetworkDispatcher) { 68 | e.dispatcher = dispatcher 69 | e.logLink.Start() 70 | 71 | go e.dispatchLoop() 72 | } 73 | 74 | // IsAttached implements stack.LinkEndpoint.IsAttached. 75 | func (e *endpoint) IsAttached() bool { 76 | return e.dispatcher != nil 77 | } 78 | 79 | // MTU implements stack.LinkEndpoint.MTU. It just forwards the request to the 80 | // lower endpoint. 81 | func (e *endpoint) MTU() uint32 { 82 | return MTU 83 | } 84 | 85 | // Capabilities implements stack.LinkEndpoint.Capabilities. It just forwards the 86 | // request to the lower endpoint. 87 | func (e *endpoint) Capabilities() stack.LinkEndpointCapabilities { 88 | 89 | return stack.LinkEndpointCapabilities(0) 90 | } 91 | 92 | // MaxHeaderLength implements the stack.LinkEndpoint interface. It just forwards 93 | // the request to the lower endpoint. 94 | func (e *endpoint) MaxHeaderLength() uint16 { 95 | 96 | return uint16(e.hdrSize) 97 | } 98 | 99 | func (e *endpoint) LinkAddress() tcpip.LinkAddress { 100 | 101 | return e.laddr 102 | } 103 | 104 | func (e *endpoint) WritePacket(r *stack.Route, _ *stack.GSO, hdr buffer.Prependable, payload buffer.VectorisedView, protocol tcpip.NetworkProtocolNumber) *tcpip.Error { 105 | if r.RemoteLinkAddress == "" { 106 | log.Fatalf("WritePacket: no remote link address found for: '%v'", r.LocalLinkAddress) 107 | } 108 | 109 | if e.hdrSize > 0 { 110 | // Add ethernet header if needed. 111 | eth := header.Ethernet(hdr.Prepend(header.EthernetMinimumSize)) 112 | ethHdr := &header.EthernetFields{ 113 | DstAddr: r.RemoteLinkAddress, 114 | Type: protocol, 115 | } 116 | 117 | // Preserve the src address if it's set in the route. 118 | if r.LocalLinkAddress != "" { 119 | ethHdr.SrcAddr = r.LocalLinkAddress 120 | } else { 121 | ethHdr.SrcAddr = e.LinkAddress() 122 | } 123 | eth.Encode(ethHdr) 124 | } 125 | 126 | views := make([]buffer.View, 0, len(payload.Views())) 127 | views = append(views, payload.Views()...) 128 | vv := buffer.NewVectorisedView(payload.Size(), views) 129 | 130 | // Fail if there is no remote address to write to 131 | cwLinkAddr := CloudwatchLinkAddress{r.LocalLinkAddress, r.RemoteLinkAddress, e.netName} 132 | 133 | // Open stream for writing (which creates if it doesn't exist) 134 | err := e.logLink.OpenLogStream(cwLinkAddr) 135 | if err != nil { 136 | log.Fatalf("WritePacket: Could not create remote log group: %v", err) 137 | } 138 | //log.Printf("WritePacket: %v -> %v (%v)", cwLinkAddr.raddr, cwLinkAddr.raddr, protocol) 139 | 140 | // Write outbound packet 141 | _, err = e.logLink.Write(cwLinkAddr, protocol, hdr.View(), vv.ToView()) 142 | if err != nil { 143 | log.Printf("WritePacket: Error writing to link buffer, dropping packet: %v", err) 144 | return nil 145 | } 146 | return nil 147 | } 148 | 149 | func (e *endpoint) ReadPacket() { 150 | vv, err := e.logLink.Read() 151 | if err != nil { 152 | log.Printf("ReadPacket: error reading: %v", err) 153 | return 154 | } 155 | var ( 156 | p tcpip.NetworkProtocolNumber 157 | remote, local tcpip.LinkAddress 158 | ) 159 | 160 | hdr := vv.Views()[0] 161 | 162 | if e.hdrSize > 0 { 163 | eth := header.Ethernet(hdr) 164 | p = eth.Type() 165 | remote = eth.SourceAddress() 166 | local = eth.DestinationAddress() 167 | } else { 168 | // We don't get any indication of what the packet is, so try to guess 169 | // if it's an IPv4 or IPv6 packet. 170 | switch header.IPVersion(hdr) { 171 | case header.IPv4Version: 172 | p = header.IPv4ProtocolNumber 173 | case header.IPv6Version: 174 | p = header.IPv6ProtocolNumber 175 | } 176 | } 177 | 178 | // Message coming from the sending link, ignore 179 | if remote != "" && remote == e.LinkAddress() { 180 | log.Printf("ReadPacket: ignoring frame, address is local") 181 | return 182 | } 183 | 184 | vv.TrimFront(e.hdrSize) 185 | 186 | e.dispatcher.DeliverNetworkPacket(e, remote, local, p, *vv) 187 | 188 | } 189 | 190 | func (e *endpoint) dispatchLoop() { 191 | for { 192 | e.ReadPacket() 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /link/aws/cloudwatch/cloudwatch_bridge.go: -------------------------------------------------------------------------------- 1 | package cloudwatch 2 | 3 | import ( 4 | "github.com/aws/aws-sdk-go/aws" 5 | "github.com/aws/aws-sdk-go/aws/session" 6 | "github.com/aws/aws-sdk-go/service/cloudwatchlogs" 7 | "github.com/google/netstack/tcpip" 8 | "github.com/google/netstack/tcpip/buffer" 9 | "github.com/google/netstack/tcpip/header" 10 | "github.com/google/netstack/tcpip/stack" 11 | "log" 12 | ) 13 | 14 | type endpointBridge struct { 15 | dispatcher stack.NetworkDispatcher 16 | laddr tcpip.LinkAddress 17 | raddr tcpip.LinkAddress 18 | netName string 19 | logLink *LogLink 20 | lower stack.LinkEndpoint // Optional wrapping of another link (tun/tap) 21 | hdrSize int 22 | p2p bool 23 | } 24 | 25 | // New creates a new endpoint for transmitting data using Amazon Cloudwathc gorups 26 | func NewBridge(opts *Options) (tcpip.LinkEndpointID, *endpointBridge) { 27 | sess, _ := session.NewSession(&aws.Config{ 28 | Region: aws.String("us-west-2")}, 29 | ) 30 | svc := cloudwatchlogs.New(sess) 31 | 32 | ep := &endpointBridge{ 33 | laddr: opts.Address, 34 | raddr: opts.RemoteAddress, 35 | netName: opts.NetworkName, 36 | p2p: opts.PointToPoint, 37 | } 38 | 39 | if opts.LinkEndpoint != 0 { 40 | ep.lower = stack.FindLinkEndpoint(opts.LinkEndpoint) 41 | } 42 | 43 | if opts.PointToPoint && opts.RemoteAddress == "" { 44 | log.Fatalf("New: Cannot create point-to-point endpoint without a remote link address.") 45 | } 46 | 47 | ep.hdrSize = 0 48 | if opts.EthernetHeader { 49 | ep.hdrSize = header.EthernetMinimumSize 50 | } 51 | 52 | ep.logLink = NewLogLink(&LogConfig{LogService: svc, Endpoint: ep, NetName: opts.NetworkName}) 53 | 54 | return stack.RegisterLinkEndpoint(ep), ep 55 | } 56 | 57 | // Attach implements the stack.LinkEndpoint interface. It saves the dispatcher 58 | // and registers with the lower endpoint as its dispatcher so that "e" is called 59 | // for inbound packets. 60 | func (e *endpointBridge) Attach(dispatcher stack.NetworkDispatcher) { 61 | e.dispatcher = dispatcher 62 | e.logLink.Start() 63 | e.lower.Attach(e) 64 | go e.dispatchLoop() 65 | } 66 | 67 | // DeliverNetworkPacket implements the stack.NetworkDispatcher interface. It is 68 | // called by the link-layer endpoint being wrapped when a packet arrives 69 | func (e *endpointBridge) DeliverNetworkPacket(rxEP stack.LinkEndpoint, srcLinkAddr, dstLinkAddr tcpip.LinkAddress, p tcpip.NetworkProtocolNumber, vv buffer.VectorisedView) { 70 | log.Printf("DeliverNetworkPacket: %v -> %v", srcLinkAddr, dstLinkAddr) 71 | 72 | //broadcast := false 73 | 74 | switch dstLinkAddr { 75 | case broadcastMAC: 76 | //broadcast = true 77 | case e.laddr: 78 | e.dispatcher.DeliverNetworkPacket(e, srcLinkAddr, dstLinkAddr, p, vv) 79 | return 80 | } 81 | 82 | route := &stack.Route{ 83 | NetProto: p, 84 | LocalLinkAddress: srcLinkAddr, 85 | RemoteLinkAddress: dstLinkAddr, 86 | } 87 | 88 | payload := vv 89 | hdr := buffer.NewPrependable(int(e.MaxHeaderLength()) + len(payload.First())) 90 | copy(hdr.Prepend(len(payload.First())), payload.ToView()) 91 | payload.RemoveFirst() 92 | 93 | // Don't write back out interface from which the frame arrived 94 | // because that causes interoperability issues with a router. 95 | if rxEP.LinkAddress() != dstLinkAddr { 96 | e.WritePacket(route, nil, hdr, payload, p) 97 | } 98 | } 99 | 100 | // IsAttached implements stack.LinkEndpoint.IsAttached. 101 | func (e *endpointBridge) IsAttached() bool { 102 | return e.dispatcher != nil 103 | } 104 | 105 | // MTU implements stack.LinkEndpoint.MTU. It just forwards the request to the 106 | // lower endpoint. 107 | func (e *endpointBridge) MTU() uint32 { 108 | return e.lower.MTU() 109 | } 110 | 111 | // Capabilities implements stack.LinkEndpoint.Capabilities. It just forwards the 112 | // request to the lower endpoint. 113 | func (e *endpointBridge) Capabilities() stack.LinkEndpointCapabilities { 114 | return e.lower.Capabilities() 115 | } 116 | 117 | // MaxHeaderLength implements the stack.LinkEndpoint interface. It just forwards 118 | // the request to the lower endpoint. 119 | func (e *endpointBridge) MaxHeaderLength() uint16 { 120 | return e.lower.MaxHeaderLength() 121 | } 122 | 123 | func (e *endpointBridge) LinkAddress() tcpip.LinkAddress { 124 | return e.lower.LinkAddress() 125 | } 126 | 127 | func (e *endpointBridge) WritePacket(r *stack.Route, _ *stack.GSO, hdr buffer.Prependable, payload buffer.VectorisedView, protocol tcpip.NetworkProtocolNumber) *tcpip.Error { 128 | if r.RemoteLinkAddress == "" { 129 | log.Fatalf("WritePacket: no remote link address found for: '%v'", r.LocalLinkAddress) 130 | } 131 | 132 | if e.hdrSize > 0 { 133 | // Add ethernet header if needed. 134 | eth := header.Ethernet(hdr.Prepend(header.EthernetMinimumSize)) 135 | ethHdr := &header.EthernetFields{ 136 | DstAddr: r.RemoteLinkAddress, 137 | Type: protocol, 138 | } 139 | 140 | // Preserve the src address if it's set in the route. 141 | if r.LocalLinkAddress != "" { 142 | ethHdr.SrcAddr = r.LocalLinkAddress 143 | } else { 144 | ethHdr.SrcAddr = e.LinkAddress() 145 | } 146 | eth.Encode(ethHdr) 147 | } 148 | 149 | views := make([]buffer.View, 0, len(payload.Views())) 150 | views = append(views, payload.Views()...) 151 | vv := buffer.NewVectorisedView(payload.Size(), views) 152 | 153 | // Fail if there is no remote address to write to 154 | cwLinkAddr := CloudwatchLinkAddress{r.LocalLinkAddress, r.RemoteLinkAddress, e.netName} 155 | 156 | // Open stream for writing (which creates if it doesn't exist) 157 | err := e.logLink.OpenLogStream(cwLinkAddr) 158 | if err != nil { 159 | log.Fatalf("WritePacket: Could not create remote log group: %v", err) 160 | } 161 | //log.Printf("WritePacket: %v -> %v (%v)", cwLinkAddr.laddr, cwLinkAddr.raddr, protocol) 162 | 163 | // Write outbound packet 164 | _, err = e.logLink.Write(cwLinkAddr, protocol, hdr.View(), vv.ToView()) 165 | if err != nil { 166 | log.Printf("WritePacket: Error writing to link buffer, dropping packet: %v", err) 167 | return nil 168 | } 169 | return nil 170 | } 171 | 172 | func (e *endpointBridge) ReadPacket() { 173 | vv, err := e.logLink.Read() 174 | if err != nil { 175 | log.Printf("ReadPacket: error reading: %v", err) 176 | return 177 | } 178 | var ( 179 | p tcpip.NetworkProtocolNumber 180 | remote, local tcpip.LinkAddress 181 | ) 182 | 183 | hdr := vv.Views()[0] 184 | 185 | if e.hdrSize > 0 { 186 | eth := header.Ethernet(hdr) 187 | p = eth.Type() 188 | remote = eth.SourceAddress() 189 | local = eth.DestinationAddress() 190 | } else { 191 | // We don't get any indication of what the packet is, so try to guess 192 | // if it's an IPv4 or IPv6 packet. 193 | switch header.IPVersion(hdr) { 194 | case header.IPv4Version: 195 | p = header.IPv4ProtocolNumber 196 | case header.IPv6Version: 197 | p = header.IPv6ProtocolNumber 198 | } 199 | } 200 | 201 | // Message coming from the sending link, ignore 202 | if remote != "" && remote == e.LinkAddress() { 203 | log.Printf("ReadPacket: ignoring frame, address is local") 204 | return 205 | } 206 | 207 | //log.Printf("ReadPacket: %v -> %v (%v)", remote, local, p) 208 | // Remove ethernet header, if exists 209 | vv.TrimFront(e.hdrSize) 210 | 211 | route := &stack.Route{ 212 | NetProto: p, 213 | LocalLinkAddress: remote, // FIXME: Is this right? 214 | RemoteLinkAddress: local, 215 | } 216 | 217 | // Create header with padding for a link-layer header 218 | transportHeader := vv.Views()[0] 219 | h := buffer.NewPrependable(int(e.MaxHeaderLength()) + len(transportHeader)) 220 | copy(h.Prepend(len(transportHeader)), transportHeader) 221 | // Remove header from vectorized view, leaving only the payload 222 | vv.RemoveFirst() 223 | e.lower.WritePacket(route, nil, h, *vv, p) 224 | 225 | } 226 | 227 | func (e *endpointBridge) dispatchLoop() { 228 | for { 229 | e.ReadPacket() 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /link/aws/cloudwatch/cloudwatch_link_address.go: -------------------------------------------------------------------------------- 1 | package cloudwatch 2 | 3 | import ( 4 | "fmt" 5 | "github.com/google/netstack/tcpip" 6 | "strings" 7 | ) 8 | 9 | type CloudwatchLinkAddress struct { 10 | laddr tcpip.LinkAddress 11 | raddr tcpip.LinkAddress 12 | netName string 13 | } 14 | 15 | func NewCloudwatchLinkAddress(laddr, raddr tcpip.LinkAddress, netName string) *CloudwatchLinkAddress { 16 | return &CloudwatchLinkAddress{laddr, raddr, netName} 17 | } 18 | 19 | func (cw *CloudwatchLinkAddress) FullPath() string { 20 | return fmt.Sprintf("%s/%s", cw.LogGroupName(), cw.LogStreamName()) 21 | } 22 | 23 | func (cw *CloudwatchLinkAddress) LogGroupName() string { 24 | return fmt.Sprintf("%v/%v", cw.netName, cw.safeLinkAddr(cw.raddr)) 25 | } 26 | 27 | func (cw *CloudwatchLinkAddress) LogStreamName() string { 28 | return cw.safeLinkAddr(cw.laddr) 29 | } 30 | 31 | func (cw *CloudwatchLinkAddress) safeLinkAddr(a tcpip.LinkAddress) string { 32 | return strings.Replace(a.String(), ":", "", -1) 33 | } 34 | 35 | func (cw *CloudwatchLinkAddress) Src() tcpip.LinkAddress { 36 | return cw.laddr 37 | } 38 | 39 | func (cw *CloudwatchLinkAddress) Dest() tcpip.LinkAddress { 40 | return cw.raddr 41 | } 42 | -------------------------------------------------------------------------------- /link/aws/cloudwatch/log_link.go: -------------------------------------------------------------------------------- 1 | package cloudwatch 2 | 3 | import ( 4 | "encoding/base64" 5 | "encoding/json" 6 | "github.com/aws/aws-sdk-go/aws" 7 | "github.com/aws/aws-sdk-go/aws/awserr" 8 | "github.com/aws/aws-sdk-go/service/cloudwatchlogs" 9 | "github.com/aws/aws-sdk-go/service/cloudwatchlogs/cloudwatchlogsiface" 10 | "github.com/google/netstack/tcpip" 11 | "github.com/google/netstack/tcpip/buffer" 12 | "github.com/google/netstack/tcpip/network/arp" 13 | "github.com/google/netstack/tcpip/network/ipv4" 14 | "github.com/google/netstack/tcpip/network/ipv6" 15 | "github.com/google/netstack/tcpip/stack" 16 | "log" 17 | ) 18 | 19 | // PacketLog represents the log event emitted from Amazon Cloudwatch 20 | type PacketLog struct { 21 | Type string `json:"type"` 22 | Src string `json:"src"` 23 | Dest string `json:"dest"` 24 | Header string `json:"header"` 25 | Payload string `json:"payload"` 26 | } 27 | 28 | // LogLink reads/writes L2 data to AWS service(s) 29 | type LogLink struct { 30 | svc cloudwatchlogsiface.CloudWatchLogsAPI 31 | ep stack.LinkEndpoint 32 | netName string 33 | readPoller *ReadPoller 34 | writePoller *WritePoller 35 | } 36 | 37 | type LogConfig struct { 38 | LogService cloudwatchlogsiface.CloudWatchLogsAPI 39 | Endpoint stack.LinkEndpoint 40 | NetName string 41 | LogGroupName string 42 | } 43 | 44 | // Log Group format `/network/link-address` 45 | // Log Stream format `/network/link-address/tx-stream-local-link-address` 46 | 47 | func NewLogLink(config *LogConfig) *LogLink { 48 | return &LogLink{svc: config.LogService, ep: config.Endpoint, netName: config.NetName, readPoller: NewReadPoller(config.LogService), writePoller: NewWritePoller(config.LogService)} 49 | } 50 | 51 | func (ll *LogLink) createLogGroup(groupName string) error { 52 | // Create log group, if it doesn't exist. 53 | _, err := ll.svc.CreateLogGroup(&cloudwatchlogs.CreateLogGroupInput{LogGroupName: aws.String(groupName)}) 54 | if awsErr, ok := err.(awserr.Error); ok { 55 | // Ignore if resource already exists 56 | if awsErr.Code() != cloudwatchlogs.ErrCodeResourceAlreadyExistsException { 57 | return err 58 | } 59 | } else if err != nil { 60 | return err 61 | } 62 | 63 | return nil 64 | } 65 | 66 | var logstreamExistsCache = map[string]bool{} 67 | 68 | var broadcastMAC = tcpip.LinkAddress([]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}) 69 | 70 | func (ll *LogLink) Start() { 71 | // Create broadcast log group and stream (/net/broadcast/local) 72 | broadcastAddrRx := CloudwatchLinkAddress{ll.ep.LinkAddress(), broadcastMAC, ll.netName} 73 | err := ll.OpenLogStream(broadcastAddrRx) 74 | if err != nil { 75 | log.Fatalf("WritePacket: Could not create remote log group: %v", err) 76 | } 77 | 78 | localReadRx := CloudwatchLinkAddress{"", ll.ep.LinkAddress(), ll.netName} 79 | err = ll.createLogGroup(localReadRx.LogGroupName()) 80 | if err != nil { 81 | log.Fatalf("OpenLogStream: Could not create remote log group: %v", err) 82 | } 83 | go ll.readPoller.ReadPollForLogGroup(localReadRx.LogGroupName()) 84 | go ll.readPoller.ReadPollForBroadcast(broadcastAddrRx.LogGroupName()) 85 | 86 | go ll.writePoller.WritePoll() 87 | } 88 | 89 | func (ll *LogLink) OpenLogStream(l CloudwatchLinkAddress) error { 90 | if _, ok := logstreamExistsCache[l.FullPath()]; !ok { 91 | // Create group 92 | err := ll.createLogGroup(l.LogGroupName()) 93 | if err != nil { 94 | log.Fatalf("OpenLogStream: Could not create remote log group: %v", err) 95 | } 96 | 97 | // Create log stream 98 | err = ll.createLogStream(l) 99 | if err != nil { 100 | log.Fatalf("WritePacket: Could not create remote log stream: %v", err) 101 | } 102 | logstreamExistsCache[l.FullPath()] = true 103 | } 104 | 105 | return nil 106 | } 107 | 108 | func (ll *LogLink) createLogStream(l CloudwatchLinkAddress) error { 109 | _, err := ll.svc.CreateLogStream(&cloudwatchlogs.CreateLogStreamInput{ 110 | LogGroupName: aws.String(l.LogGroupName()), 111 | LogStreamName: aws.String(l.LogStreamName()), 112 | }) 113 | if err != nil { 114 | if awsErr, ok := err.(awserr.Error); ok { 115 | if awsErr.Code() == cloudwatchlogs.ErrCodeResourceAlreadyExistsException { 116 | err = nil 117 | } 118 | } 119 | if err != nil { 120 | return err 121 | } 122 | } 123 | 124 | return nil 125 | } 126 | 127 | // Read reads one packet from the internal buffers 128 | func (ll *LogLink) Read() (*buffer.VectorisedView, error) { 129 | select { 130 | case event := <-ll.readPoller.Cr: 131 | if event.err != nil { 132 | log.Printf("Read: Poll Error: %v", event.err) 133 | break 134 | } 135 | 136 | // Unmarshal 137 | var packetLog PacketLog 138 | err := json.Unmarshal(event.data, &packetLog) 139 | if err != nil { 140 | return nil, err 141 | } 142 | 143 | h := make([]byte, 1024) 144 | n, err := base64.StdEncoding.Decode(h, []byte(packetLog.Header)) 145 | if err != nil { 146 | return nil, err 147 | } 148 | header := buffer.NewViewFromBytes(h[:n]) 149 | 150 | j := make([]byte, ll.ep.MTU()) 151 | m, err := base64.StdEncoding.Decode(j, []byte(packetLog.Payload)) 152 | if err != nil { 153 | return nil, err 154 | } 155 | payload := buffer.NewViewFromBytes(j[:m]) 156 | 157 | vv := buffer.NewVectorisedView(n+m, []buffer.View{header, payload}) 158 | return &vv, nil 159 | } 160 | return nil, nil 161 | } 162 | 163 | func (ll *LogLink) StringToProtocol(protocol string) tcpip.NetworkProtocolNumber { 164 | switch protocol { 165 | case ipv4.ProtocolName: 166 | return ipv4.ProtocolNumber 167 | case ipv6.ProtocolName: 168 | return ipv6.ProtocolNumber 169 | case arp.ProtocolName: 170 | return arp.ProtocolNumber 171 | default: 172 | return 0 173 | } 174 | } 175 | 176 | func (ll *LogLink) ProtocolToString(protocol tcpip.NetworkProtocolNumber) string { 177 | switch protocol { 178 | case ipv4.ProtocolNumber: 179 | return ipv4.ProtocolName 180 | case ipv6.ProtocolNumber: 181 | return ipv6.ProtocolName 182 | case arp.ProtocolNumber: 183 | return arp.ProtocolName 184 | default: 185 | return "unknown" 186 | } 187 | } 188 | 189 | // Write writes one packet to the internal buffers 190 | func (ll *LogLink) Write(l CloudwatchLinkAddress, protocol tcpip.NetworkProtocolNumber, header []byte, payload []byte) (int, error) { 191 | // todo: replace with pcap-friendly format 192 | pl := PacketLog{ll.ProtocolToString(protocol), l.Src().String(), l.Dest().String(), 193 | base64.StdEncoding.EncodeToString(header), base64.StdEncoding.EncodeToString(payload)} 194 | plBytes, err := json.Marshal(pl) 195 | if err != nil { 196 | return 0, err 197 | } 198 | ll.writePoller.Cw <- WritePollInput{plBytes, &l} 199 | return len(plBytes), nil 200 | } 201 | -------------------------------------------------------------------------------- /link/aws/cloudwatch/read_poller.go: -------------------------------------------------------------------------------- 1 | package cloudwatch 2 | 3 | import ( 4 | "github.com/aws/aws-sdk-go/aws" 5 | "github.com/aws/aws-sdk-go/service/cloudwatchlogs" 6 | "github.com/aws/aws-sdk-go/service/cloudwatchlogs/cloudwatchlogsiface" 7 | "log" 8 | "time" 9 | ) 10 | 11 | type ReadPollOutput struct { 12 | data []byte 13 | err error 14 | } 15 | 16 | func (p *ReadPollOutput) Data() []byte { 17 | return p.data 18 | } 19 | 20 | func (p *ReadPollOutput) Error() error { 21 | return p.err 22 | } 23 | 24 | type ReadPoller struct { 25 | client cloudwatchlogsiface.CloudWatchLogsAPI 26 | readThrottle <-chan time.Time 27 | broadcastThrottle <-chan time.Time 28 | limit int 29 | nextTokens map[string]*string 30 | startTimes map[string]int64 31 | 32 | Cr chan ReadPollOutput 33 | } 34 | 35 | func NewReadPoller(client cloudwatchlogsiface.CloudWatchLogsAPI) *ReadPoller { 36 | p := &ReadPoller{ 37 | readThrottle: time.Tick(time.Second / 4), 38 | broadcastThrottle: time.Tick(time.Second / 1), 39 | limit: 32, 40 | nextTokens: map[string]*string{}, 41 | startTimes: map[string]int64{}, 42 | client: client, 43 | Cr: make(chan ReadPollOutput, 32), 44 | } 45 | return p 46 | } 47 | 48 | func (p *ReadPoller) fetch(groupName string) { 49 | params := &cloudwatchlogs.FilterLogEventsInput{ 50 | LogGroupName: aws.String(groupName), 51 | NextToken: p.nextTokens[groupName], 52 | Interleaved: aws.Bool(true), 53 | StartTime: aws.Int64(p.startTimes[groupName]), 54 | } 55 | 56 | resp, err := p.client.FilterLogEvents(params) 57 | if err != nil { 58 | p.Cr <- ReadPollOutput{err: err} 59 | return 60 | } 61 | 62 | // We want to re-use the existing token in the event that 63 | // NextForwardToken is nil, which means there's no new messages to 64 | // consume. 65 | if resp.NextToken != nil { 66 | p.nextTokens[groupName] = resp.NextToken 67 | } 68 | 69 | // If there are no messages, return so that the consumer can read again. 70 | if len(resp.Events) == 0 { 71 | return 72 | } 73 | for _, event := range resp.Events { 74 | p.Cr <- ReadPollOutput{[]byte(*event.Message), nil} 75 | p.startTimes[groupName] = aws.Int64Value(event.Timestamp) + 1 76 | } 77 | } 78 | 79 | func (p *ReadPoller) ReadPollForBroadcast(groupName string) { 80 | log.Printf("Reading bcast poll: %v", groupName) 81 | p.startTimes[groupName] = time.Now().Unix() * 1000 82 | for { 83 | <-p.broadcastThrottle 84 | p.fetch(groupName) 85 | } 86 | } 87 | 88 | func (p *ReadPoller) ReadPollForLogGroup(groupName string) { 89 | log.Printf("Reading stream poll: %v", groupName) 90 | p.startTimes[groupName] = time.Now().Unix() * 1000 91 | for { 92 | <-p.readThrottle 93 | p.fetch(groupName) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /link/aws/cloudwatch/write_poller.go: -------------------------------------------------------------------------------- 1 | package cloudwatch 2 | 3 | import ( 4 | "github.com/aws/aws-sdk-go/aws" 5 | "github.com/aws/aws-sdk-go/aws/awserr" 6 | "github.com/aws/aws-sdk-go/service/cloudwatchlogs" 7 | "github.com/aws/aws-sdk-go/service/cloudwatchlogs/cloudwatchlogsiface" 8 | "log" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | type WritePollInput struct { 14 | data []byte 15 | cwLink *CloudwatchLinkAddress 16 | } 17 | 18 | func NewWritePollInput(data []byte, link *CloudwatchLinkAddress) WritePollInput { 19 | return WritePollInput{data, link} 20 | } 21 | 22 | type WritePoller struct { 23 | client cloudwatchlogsiface.CloudWatchLogsAPI 24 | writeThrottle <-chan time.Time 25 | limit int 26 | sequenceTokens map[string]*string 27 | Cw chan WritePollInput 28 | } 29 | 30 | func NewWritePoller(client cloudwatchlogsiface.CloudWatchLogsAPI) *WritePoller { 31 | p := &WritePoller{ 32 | writeThrottle: time.Tick(time.Second / 5), 33 | limit: 16, 34 | client: client, 35 | sequenceTokens: map[string]*string{}, 36 | Cw: make(chan WritePollInput, 16), 37 | } 38 | return p 39 | } 40 | 41 | func (p *WritePoller) putLogEvents(events []*cloudwatchlogs.InputLogEvent, sequenceToken *string, groupName string, streamName string) (nextSequenceToken *string, err error) { 42 | resp, err := p.client.PutLogEvents(&cloudwatchlogs.PutLogEventsInput{ 43 | LogEvents: events, 44 | LogGroupName: aws.String(groupName), 45 | LogStreamName: aws.String(streamName), 46 | SequenceToken: sequenceToken, 47 | }) 48 | if err != nil { 49 | if awsErr, ok := err.(awserr.Error); ok { 50 | if awsErr.Code() != cloudwatchlogs.ErrCodeInvalidSequenceTokenException { 51 | log.Printf( 52 | "Failed to put log: events: errorCode: %s message: %s, origError: %s log-group: %s log-stream: %s", 53 | awsErr.Code(), 54 | awsErr.Message(), 55 | awsErr.OrigErr(), 56 | groupName, 57 | streamName, 58 | ) 59 | } 60 | } else { 61 | log.Printf("Failed to put log: %s", err) 62 | } 63 | 64 | return nil, err 65 | } 66 | sequenceToken = resp.NextSequenceToken 67 | return sequenceToken, nil 68 | } 69 | 70 | func (p *WritePoller) flush(events []*cloudwatchlogs.InputLogEvent, fullPath string, groupName string, streamName string) error { 71 | nextSequenceToken, err := p.putLogEvents(events, p.sequenceTokens[fullPath], groupName, streamName) 72 | if err != nil { 73 | if awsErr, ok := err.(awserr.Error); ok { 74 | if awsErr.Code() == cloudwatchlogs.ErrCodeDataAlreadyAcceptedException { 75 | // already submitted, just grab the correct sequence token 76 | parts := strings.Split(awsErr.Message(), " ") 77 | nextSequenceToken = &parts[len(parts)-1] 78 | // TODO log locally... 79 | log.Println( 80 | "Data already accepted, ignoring error", 81 | "errorCode: ", awsErr.Code(), 82 | "message: ", awsErr.Message(), 83 | "logGroupName: ", groupName, 84 | "logStreamName: ", streamName, 85 | ) 86 | err = nil 87 | } else if awsErr.Code() == cloudwatchlogs.ErrCodeInvalidSequenceTokenException { 88 | 89 | // sequence code is bad, grab the correct one and retry 90 | parts := strings.Split(awsErr.Message(), " ") 91 | token := parts[len(parts)-1] 92 | nextSequenceToken, err = p.putLogEvents(events, aws.String(token), groupName, streamName) 93 | } 94 | } 95 | } 96 | 97 | if err != nil { 98 | log.Println("error flushing", err) 99 | return err 100 | } else { 101 | p.sequenceTokens[fullPath] = nextSequenceToken 102 | } 103 | return err 104 | } 105 | 106 | type PutEventInput struct { 107 | groupName string 108 | streamName string 109 | fullPath string 110 | } 111 | 112 | func (p *WritePoller) WritePoll() { 113 | for { 114 | <-p.writeThrottle 115 | events := make(map[PutEventInput][]*cloudwatchlogs.InputLogEvent, 0) 116 | select { 117 | case writeInput := <-p.Cw: 118 | cwInput := &cloudwatchlogs.InputLogEvent{ 119 | Message: aws.String(string(writeInput.data)), 120 | Timestamp: aws.Int64(time.Now().UnixNano() / 1000000), 121 | } 122 | pei := PutEventInput{writeInput.cwLink.LogGroupName(), writeInput.cwLink.LogStreamName(), writeInput.cwLink.FullPath()} 123 | events[pei] = append(events[pei], cwInput) 124 | default: 125 | continue 126 | } 127 | if len(events) > 0 { 128 | // Flush written events for each unique EndpointLogStream 129 | for k, v := range events { 130 | err := p.flush(v, k.fullPath, k.groupName, k.streamName) 131 | if err != nil { 132 | log.Printf("Error flushing: %v", err) 133 | } 134 | } 135 | } else { 136 | log.Printf("WritePoll: no events to flush") 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /link/aws/tag/buffy.go: -------------------------------------------------------------------------------- 1 | package tag 2 | 3 | import ( 4 | "bytes" 5 | "encoding/base64" 6 | "errors" 7 | "fmt" 8 | "io" 9 | ) 10 | 11 | type Buffy struct { 12 | off int 13 | cap int 14 | encodedBuf []byte 15 | } 16 | 17 | var ErrOverCapacity = errors.New("Buffy: over capacity") 18 | 19 | func NewBuffy(cap int) *Buffy { 20 | return &Buffy{off: 0, cap: cap, encodedBuf: make([]byte, 0, cap)} 21 | } 22 | 23 | func (b *Buffy) String() string { 24 | return fmt.Sprintf("Buffer (cap: %d, len: %d)", 25 | cap(b.encodedBuf), len(b.encodedBuf)) 26 | } 27 | 28 | func (b *Buffy) WriteUnencoded(p []byte) (n int, err error) { 29 | encodedBytes := make([]byte, base64.StdEncoding.EncodedLen(len(p))) 30 | base64.StdEncoding.Encode(encodedBytes, p) 31 | return b.Write(encodedBytes) 32 | } 33 | 34 | func (b *Buffy) DecodedBytes() ([]byte, error) { 35 | // read all 36 | encodedBytes := make([]byte, len(b.encodedBuf)) 37 | n, err := b.Read(encodedBytes) 38 | if err != nil { 39 | return nil, err 40 | } 41 | 42 | decodedBytes := make([]byte, base64.StdEncoding.DecodedLen(n)) 43 | m, err := base64.StdEncoding.Decode(decodedBytes, encodedBytes[:n]) 44 | if err != nil { 45 | return nil, err 46 | } 47 | return decodedBytes[:m], nil 48 | } 49 | 50 | func (b *Buffy) resize(p int) (int, error) { 51 | m := len(b.encodedBuf) 52 | if p <= b.cap-m { 53 | b.encodedBuf = b.encodedBuf[:m+p] 54 | return m + p, nil 55 | } else { 56 | return 0, ErrOverCapacity 57 | } 58 | } 59 | 60 | func (b *Buffy) Write(p []byte) (n int, err error) { 61 | m := len(b.encodedBuf) 62 | _, err = b.resize(len(p)) 63 | if err != nil { 64 | return 0, ErrOverCapacity 65 | } 66 | 67 | return copy(b.encodedBuf[m:], p), nil 68 | } 69 | 70 | func (b *Buffy) empty() bool { 71 | return len(b.encodedBuf) <= b.off 72 | } 73 | 74 | func (b *Buffy) EncodedBytes() []byte { 75 | return b.encodedBuf 76 | } 77 | 78 | func (b *Buffy) EncodedBytesString() string { 79 | return string(b.encodedBuf) 80 | } 81 | 82 | func (b *Buffy) Reset() { 83 | b.encodedBuf = b.encodedBuf[:0] 84 | b.off = 0 85 | } 86 | 87 | func (b *Buffy) Replace(p []byte) (bool, error) { 88 | if bytes.Equal(p, b.encodedBuf) { 89 | return false, nil 90 | } 91 | b.Reset() 92 | _, err := b.resize(len(p)) 93 | if err != nil { 94 | return false, err 95 | } 96 | copy(b.encodedBuf, p) 97 | return true, nil 98 | } 99 | 100 | func (b *Buffy) Len() int { 101 | return len(b.encodedBuf) - b.off 102 | } 103 | 104 | func (b *Buffy) Offset() int { 105 | return b.off 106 | } 107 | 108 | func (b *Buffy) Read(p []byte) (n int, err error) { 109 | if b.empty() { 110 | b.Reset() 111 | if len(p) == 0 { 112 | return 0, nil 113 | } 114 | return 0, io.EOF 115 | } 116 | n = copy(p, b.encodedBuf[b.off:]) 117 | b.off += n 118 | return n, nil 119 | } 120 | -------------------------------------------------------------------------------- /link/aws/tag/buffy_test.go: -------------------------------------------------------------------------------- 1 | package tag 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "testing" 7 | ) 8 | 9 | func setup(cap int) *Buffy { 10 | return NewBuffy(cap) 11 | } 12 | 13 | func TestBuffy_Read(t *testing.T) { 14 | capacity := 16 15 | b := setup(capacity) 16 | originalText := []byte("helloworld") 17 | inputBytes := []byte("aGVsbG93b3JsZA==") 18 | _, err := b.Write(inputBytes) 19 | if err != nil { 20 | t.Errorf("Unexpected write error %v", err) 21 | return 22 | } 23 | 24 | // Replace has no impact on buffer cursor position 25 | b.Replace(inputBytes) 26 | b.Replace(inputBytes) 27 | 28 | decodedBytes, err := b.DecodedBytes() 29 | if err != nil { 30 | t.Errorf("Unexpected read error %v", err) 31 | return 32 | } 33 | 34 | if !bytes.Equal(originalText, decodedBytes) { 35 | t.Errorf("Expected decoded to be '%s' (len: %d) got '%s' (len: %d)", originalText, len(originalText), decodedBytes, len(decodedBytes)) 36 | return 37 | } 38 | if b.Len() != 0 { 39 | t.Errorf("0: Expected buffer length to be 0") 40 | return 41 | } 42 | 43 | _, err = b.DecodedBytes() 44 | if err != io.EOF { 45 | t.Errorf("Expected read error %v", err) 46 | return 47 | } 48 | 49 | if b.Len() != 0 { 50 | t.Errorf("1: Expected buffer length to be 0") 51 | return 52 | } 53 | } 54 | 55 | func TestBuffy_Replace(t *testing.T) { 56 | capacity := 16 57 | b := setup(capacity) 58 | tables := []struct { 59 | write []byte 60 | replaceBytes []byte 61 | writeErr error 62 | }{ 63 | {make([]byte, 0), []byte("aGVsbG8="), nil}, 64 | {make([]byte, 3), []byte("aGVsbG8="), nil}, 65 | {[]byte("aGVsbG8="), make([]byte, 3), nil}, 66 | {make([]byte, 3), []byte("aGVsbG8="), nil}, 67 | } 68 | for i, table := range tables { 69 | b.Reset() 70 | _, err := b.Write(table.write) 71 | if err != table.writeErr { 72 | t.Errorf("[%d] Expected write error %v, got: %v", i, table.writeErr, err) 73 | continue 74 | } 75 | b.Replace(table.replaceBytes) 76 | if !bytes.Equal(table.replaceBytes, b.EncodedBytes()) { 77 | t.Errorf("[%d] Expected internal buffer %v, got: %v", i, table.replaceBytes, b.EncodedBytes()) 78 | continue 79 | } 80 | } 81 | } 82 | 83 | func TestBuffy_WriteEncoded(t *testing.T) { 84 | capacity := 16 85 | b := setup(capacity) 86 | tables := []struct { 87 | write []byte 88 | internalBuffer []byte 89 | read []byte 90 | writeErr error 91 | readErr error 92 | }{ 93 | {make([]byte, 0), make([]byte, 0), make([]byte, 0), nil, nil}, 94 | {make([]byte, 3), []byte("AAAA"), make([]byte, 3), nil, nil}, // is this right? 95 | {[]byte("hello"), []byte("aGVsbG8="), []byte("hello"), nil, nil}, 96 | {[]byte("this-is-over-capacity"), make([]byte, 0), make([]byte, 0), ErrOverCapacity, nil}, 97 | } 98 | 99 | for i, table := range tables { 100 | b.Reset() 101 | _, err := b.WriteUnencoded(table.write) 102 | if err != table.writeErr { 103 | t.Errorf("[%d] Expected write error %v, got: %v", i, table.writeErr, err) 104 | continue 105 | } 106 | if !bytes.Equal(table.internalBuffer, b.EncodedBytes()) { 107 | t.Errorf("[%d] Expected internal buffer %v, got: %v", i, table.internalBuffer, b.EncodedBytes()) 108 | continue 109 | } 110 | //decodedBytes := make([]byte, 4) 111 | decodedBytes, err := b.DecodedBytes() 112 | if err != table.readErr { 113 | t.Errorf("[%d] Expected read error %v, got: %v", i, table.readErr, err) 114 | continue 115 | } 116 | if !bytes.Equal(table.read, decodedBytes) { 117 | t.Errorf("[%d] Expected read buffer %v (\"%s\"), got: %v", i, table.read, table.read, decodedBytes) 118 | continue 119 | } 120 | } 121 | } 122 | func TestBuffy_ReadWriteRaw(t *testing.T) { 123 | b := setup(4) 124 | tables := []struct { 125 | write []byte 126 | read []byte 127 | writtenBytes int 128 | readBytes int 129 | readBufSize int 130 | readErr error 131 | writeErr error 132 | }{ 133 | {make([]byte, 0), make([]byte, 0), 0, 0, 0, nil, nil}, 134 | {[]byte{0x1, 0x2, 0x3}, []byte{0x1, 0x2, 0x3}, 3, 3, 3, nil, nil}, 135 | {[]byte{0x1, 0x2, 0x3, 0x4}, []byte{0x1, 0x2, 0x3, 0x4}, 4, 4, 4, nil, nil}, 136 | {[]byte{0x1, 0x2, 0x3, 0x4}, []byte{0x1, 0x2, 0x3, 0x4}, 4, 4, 16, nil, nil}, 137 | {[]byte{0x1, 0x2, 0x3, 0x4}, []byte{0x1, 0x2}, 4, 2, 2, nil, nil}, 138 | {[]byte{0x1, 0x2, 0x3, 0x4, 0x5}, []byte{}, 0, 0, 0, nil, ErrOverCapacity}, 139 | } 140 | for i, table := range tables { 141 | b.Reset() 142 | n, err := b.Write(table.write) 143 | if err != table.writeErr { 144 | t.Errorf("[%d] Expected write error %v, got: %v", i, table.writeErr, err) 145 | continue 146 | } 147 | if n != table.writtenBytes { 148 | t.Errorf("[%d] Expected %d bytes written, got %d", i, table.writtenBytes, n) 149 | continue 150 | } 151 | 152 | readBuffer := make([]byte, table.readBytes) 153 | m, err := b.Read(readBuffer) 154 | if err != table.readErr { 155 | t.Errorf("[%d] Expected read error %v, got: %v", i, table.readErr, err) 156 | continue 157 | } 158 | if m != table.readBytes { 159 | t.Errorf("[%d] Expected %d bytes read, got: %d", i, table.writtenBytes, m) 160 | continue 161 | } 162 | if !bytes.Equal(table.read, readBuffer) { 163 | t.Errorf("[%d] Expected read buffer %v, got: %v", i, table.read, readBuffer) 164 | continue 165 | } 166 | 167 | if b.Len() != (table.writtenBytes - table.readBytes) { 168 | t.Errorf("[%d] Expected buffer length to be %d, got: %d", i, (table.writtenBytes - table.readBytes), b.Len()) 169 | } 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /link/aws/tag/ring.go: -------------------------------------------------------------------------------- 1 | package tag 2 | 3 | import ( 4 | "container/ring" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "log" 9 | ) 10 | 11 | type TagRingType uint8 12 | 13 | const ( 14 | TransmitType TagRingType = 0 15 | ReceiveType TagRingType = 1 16 | ) 17 | 18 | type TagRing struct { 19 | r *ring.Ring 20 | ringSize int 21 | avail int // received packets in RX rings, empty slots in TX rings 22 | lastWriteOp int // position in ring of last write operation 23 | lastReadOp int // position in ring of last read operation 24 | t TagRingType 25 | } 26 | 27 | type TagBuffer struct { 28 | b *Buffy 29 | ringPosition int 30 | } 31 | 32 | const TagRingNoOp = -1 33 | 34 | func NewTagRing(cap int, t TagRingType) *TagRing { 35 | ring := ring.New(cap) 36 | for i := 0; i < cap; i++ { 37 | // TODO: replace with buffer config 38 | ring.Value = &TagBuffer{b: NewBuffy(255), ringPosition: i} // fixed size packet buffers 39 | ring = ring.Next() 40 | } 41 | 42 | var a int 43 | if t == TransmitType { 44 | a = cap 45 | } else if t == ReceiveType { 46 | a = 0 47 | } 48 | 49 | return &TagRing{r: ring, avail: a, lastReadOp: TagRingNoOp, lastWriteOp: TagRingNoOp, ringSize: cap, t: t} 50 | } 51 | 52 | var FullBuffers = errors.New("TagRing: Full Buffers") 53 | 54 | func (tr *TagRing) Reset() { 55 | tr.lastWriteOp = TagRingNoOp 56 | tr.lastReadOp = TagRingNoOp 57 | 58 | if tr.t == TransmitType { 59 | tr.avail = tr.ringSize 60 | } else if tr.t == ReceiveType { 61 | tr.avail = 0 62 | } 63 | 64 | tr.r.Do(func(p interface{}) { 65 | p.(*TagBuffer).b.Reset() 66 | }) 67 | } 68 | 69 | func (tr *TagRing) Seek(ndx int) *TagBuffer { 70 | if ndx > tr.ringSize || ndx < 0 { 71 | panic("Seek: out of bounds ring position") 72 | } 73 | 74 | for { 75 | cur := tr.r.Value.(*TagBuffer).ringPosition 76 | if ndx == cur { 77 | break 78 | } 79 | tr.r = tr.r.Next() 80 | } 81 | 82 | return tr.r.Value.(*TagBuffer) 83 | } 84 | 85 | // Replace injects a byte slice into a position in the ring. 86 | func (tr *TagRing) Replace(ndx int, p []byte) error { 87 | tr.Seek(ndx) 88 | replaced, err := tr.r.Value.(*TagBuffer).b.Replace(p) 89 | 90 | if replaced && tr.t == TransmitType { 91 | tr.avail-- 92 | } 93 | 94 | if replaced && tr.t == ReceiveType { 95 | tr.avail++ 96 | } 97 | 98 | return err 99 | } 100 | 101 | // Write encodes a byte slice to the current ring buffer 102 | func (tr *TagRing) Write(p []byte) (int, error) { 103 | if tr.t == ReceiveType { 104 | log.Fatalf("TagRing: fatal cannot write to a receive ring") 105 | } 106 | 107 | if tr.avail == 0 { 108 | return 0, FullBuffers 109 | } 110 | buf := tr.nextWriteBuffer() 111 | n, err := buf.b.WriteUnencoded(p) 112 | if err != nil { 113 | return 0, err 114 | } 115 | if n > 0 { 116 | tr.lastWriteOp = buf.ringPosition 117 | tr.avail-- // one less empty slot 118 | } 119 | return n, err 120 | } 121 | 122 | func (tr *TagRing) currentBuffer() *Buffy { 123 | return tr.r.Value.(*Buffy) 124 | } 125 | 126 | func (tr *TagRing) nextWriteBuffer() *TagBuffer { 127 | if tr.avail == 0 { 128 | return nil 129 | } 130 | curRing := tr.r 131 | // todo: instead of loop just keep track of this with new var (?) 132 | for i := 0; i < tr.ringSize; i++ { 133 | curBuf := curRing.Value.(*TagBuffer) 134 | if curBuf.b.Len() == 0 { 135 | return curRing.Value.(*TagBuffer) 136 | } 137 | curRing = curRing.Next() 138 | } 139 | panic(fmt.Sprintf("TagRing: Encountered writable buffer (avail: %d) with no available slots.", tr.avail)) 140 | } 141 | 142 | func (tr *TagRing) nextReadBuffer() *TagBuffer { 143 | if tr.avail == 0 { 144 | return nil 145 | } 146 | curRing := tr.r 147 | // todo: instead of loop just keep track of this with new var (?) 148 | for i := 0; i < tr.ringSize; i++ { 149 | curBuf := curRing.Value.(*TagBuffer) 150 | if curBuf.b.Len() > 0 { 151 | return curRing.Value.(*TagBuffer) 152 | } 153 | curRing = curRing.Next() 154 | } 155 | panic(fmt.Sprintf("TagRing: Encountered readable buffer (avail: %d) with no available slots.", tr.avail)) 156 | } 157 | 158 | // Read decodes the value of the current ring buffer to a byte slice 159 | func (tr *TagRing) Read(p []byte) (int, error) { 160 | if tr.t == TransmitType { 161 | log.Fatalf("TagRing: Cannot read from a transmit ring") 162 | } 163 | 164 | if tr.avail == 0 { 165 | return 0, io.EOF 166 | } 167 | buf := tr.nextReadBuffer() 168 | b, err := buf.b.DecodedBytes() 169 | 170 | if err != nil { 171 | return 0, err 172 | } 173 | n := copy(p, b) 174 | if n > 0 { 175 | tr.lastReadOp = buf.ringPosition 176 | tr.avail-- // one less slot to read 177 | } 178 | return n, nil 179 | } 180 | -------------------------------------------------------------------------------- /link/aws/tag/ring_test.go: -------------------------------------------------------------------------------- 1 | package tag 2 | 3 | import ( 4 | "io" 5 | "log" 6 | "testing" 7 | ) 8 | 9 | func TestRing_Read(t *testing.T) { 10 | 11 | r := NewTagRing(3, ReceiveType) 12 | r.Replace(0, []byte("aGVsbG8=")) 13 | r.Replace(1, []byte("MTIzNA==")) 14 | r.Replace(1, []byte("MTIzNA==")) 15 | 16 | tables := []struct { 17 | read []byte 18 | readBytes int 19 | readError error 20 | avail int 21 | }{ 22 | {[]byte("hello"), 4, nil, 1}, 23 | {[]byte("1234"), 5, nil, 0}, 24 | {nil, 0, io.EOF, 0}, 25 | } 26 | 27 | for i, table := range tables { 28 | p := make([]byte, 255) 29 | n, err := r.Read(p) 30 | if err != table.readError { 31 | t.Fatalf("[%d] TestRing_Read: got unexpected error: %v", i, err) 32 | } 33 | if n != table.readBytes { 34 | t.Fatalf("[%d] TestRing_Read: error reading, expected %d, got %d", i, table.readBytes, n) 35 | } 36 | if r.avail != table.avail { 37 | t.Fatalf("[%d] TestRing_Read: expected %v available buffers, got %v", i, table.avail, r.avail) 38 | } 39 | } 40 | } 41 | 42 | func TestRing_Write(t *testing.T) { 43 | r := NewTagRing(4, TransmitType) 44 | r.Replace(0, []byte("aGVsbG8=")) 45 | 46 | tables := []struct { 47 | write []byte 48 | writeBytes int 49 | writeError error 50 | avail int 51 | }{ 52 | {[]byte{0x1, 0x2, 0x3}, 4, nil, 2}, 53 | {[]byte{0x4, 0x5, 0x6}, 4, nil, 1}, 54 | {make([]byte, 0), 0, nil, 1}, 55 | {[]byte{0x7, 0x8, 0x9}, 4, nil, 0}, 56 | {[]byte{0x7, 0x8, 0x9}, 0, FullBuffers, 0}, 57 | } 58 | 59 | for i, table := range tables { 60 | n, err := r.Write(table.write) 61 | if err != table.writeError { 62 | t.Fatalf("[%d] TestRing_Write: got unexpected error: %v", i, err) 63 | } 64 | if n != table.writeBytes { 65 | log.Printf("[%d] TestRing_Write: error writing, got %d", i, n) 66 | } 67 | if r.avail != table.avail { 68 | t.Fatalf("[%d] TestRing_Write: expected 2 available buffers, got %v", i, r.avail) 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /link/aws/tag/tag.go: -------------------------------------------------------------------------------- 1 | package tag 2 | 3 | import ( 4 | "github.com/aws/aws-sdk-go/aws" 5 | "github.com/aws/aws-sdk-go/aws/session" 6 | "github.com/aws/aws-sdk-go/service/lambda" 7 | "github.com/google/netstack/tcpip" 8 | "github.com/google/netstack/tcpip/buffer" 9 | "github.com/google/netstack/tcpip/header" 10 | "github.com/google/netstack/tcpip/network/ipv4" 11 | "github.com/google/netstack/tcpip/stack" 12 | "io" 13 | "log" 14 | ) 15 | 16 | type endpoint struct { 17 | dispatcher stack.NetworkDispatcher 18 | tagLink *TagLink 19 | stats *AwsStats 20 | laddr tcpip.LinkAddress 21 | raddr tcpip.LinkAddress 22 | } 23 | 24 | // Options specify the details about the AWS service-based endpoint to be created. 25 | type Options struct { 26 | LocalArn string 27 | RemoteArn string 28 | LocalAddress tcpip.LinkAddress 29 | RemoteAddress tcpip.LinkAddress 30 | } 31 | 32 | // AwsStats collects link-specific stats. 33 | type AwsStats struct { 34 | RxPackets uint32 35 | TxPackets uint32 36 | TxErrors uint32 37 | RxErrors uint32 38 | } 39 | 40 | // New creates a new endpoint for transmitting data using AWS Lambda tags. 41 | func New(opts *Options) tcpip.LinkEndpointID { 42 | ep := &endpoint{ 43 | laddr: opts.LocalAddress, 44 | raddr: opts.RemoteAddress, 45 | stats: &AwsStats{}, 46 | } 47 | ep.tagLink = newTagLink(opts.LocalArn, opts.RemoteArn, ep) 48 | log.Printf("New AWS Link: local %s, remote %s", ep.laddr, ep.raddr) 49 | return stack.RegisterLinkEndpoint(ep) 50 | } 51 | 52 | func newTagLink(localArn string, remoteArn string, e *endpoint) *TagLink { 53 | sess, _ := session.NewSession(&aws.Config{ 54 | Region: aws.String("us-west-2")}, 55 | ) 56 | svc := lambda.New(sess, &aws.Config{Region: aws.String("us-west-2")}) 57 | config := TagConfig{ 58 | LambdaService: svc, 59 | Endpoint: e, 60 | TxArn: remoteArn, 61 | RxArn: localArn, 62 | } 63 | return NewTagLink(&config) 64 | } 65 | 66 | // Attach implements stack.LinkEndpoint.Attach. It just saves the stack network- 67 | // layer dispatcher for later use when packets need to be dispatched. 68 | func (e *endpoint) Attach(dispatcher stack.NetworkDispatcher) { 69 | e.dispatcher = dispatcher 70 | e.tagLink.StartPolling() 71 | go e.dispatchLoop() 72 | } 73 | 74 | func (e *endpoint) dispatchLoop() { 75 | for { 76 | decoded, err := e.readSinglePacket(BufConfig[0]) 77 | if err != nil { 78 | log.Printf("dispatchLoop: Error reading single packet: %v", err) 79 | e.stats.RxErrors++ 80 | continue 81 | } 82 | if len(decoded) > 0 { 83 | e.stats.RxPackets++ 84 | e.dispatchSinglePacket(decoded) 85 | } 86 | } 87 | } 88 | 89 | func (e *endpoint) readSinglePacket(size int) ([]byte, error) { 90 | buf := make([]byte, size) 91 | n, err := e.tagLink.Read(buf) 92 | if err != nil && err != io.EOF { 93 | return nil, err 94 | } 95 | 96 | return buf[:n], nil 97 | } 98 | 99 | func (e *endpoint) dispatchSinglePacket(decoded []byte) bool { 100 | 101 | //log.Printf("dispatchLoop: read buffer %v", decoded) 102 | ipv4Packet := header.IPv4(decoded) 103 | if ipv4Packet.IsValid(len(decoded)) { 104 | //log.Printf("dispatchLoop: valid ipv4 packet received, src: %s, dest: %s", ipv4Packet.SourceAddress(), ipv4Packet.DestinationAddress()) 105 | vv := buffer.NewViewFromBytes(decoded).ToVectorisedView() 106 | e.dispatcher.DeliverNetworkPacket(e, "", "", ipv4.ProtocolNumber, vv) 107 | return true 108 | } 109 | log.Printf("dispatchLoop: Invalid ipv4 packet recvd: %v", decoded) 110 | return false 111 | } 112 | 113 | // IsAttached implements stack.LinkEndpoint.IsAttached. 114 | func (e *endpoint) IsAttached() bool { 115 | return e.dispatcher != nil 116 | } 117 | 118 | // MTU implements stack.LinkEndpoint.MTU. 119 | // Maximum tag length 120 | func (*endpoint) MTU() uint32 { 121 | return 189 122 | } 123 | 124 | // Capabilities implements stack.LinkEndpoint.Capabilities. Loopback advertises 125 | // itself as supporting checksum offload, but in reality it's just omitted. 126 | func (*endpoint) Capabilities() stack.LinkEndpointCapabilities { 127 | return stack.LinkEndpointCapabilities(0) 128 | //return stack.CapabilityChecksumOffload | stack.CapabilitySaveRestore | stack.CapabilityLoopback 129 | } 130 | 131 | // MaxHeaderLength implements stack.LinkEndpoint.MaxHeaderLength. Given that the 132 | // loopback interface doesn't have a header, it just returns 0. 133 | func (*endpoint) MaxHeaderLength() uint16 { 134 | return 0 135 | } 136 | 137 | // LinkAddress returns the link address of this endpoint. 138 | func (e *endpoint) LinkAddress() tcpip.LinkAddress { 139 | return e.laddr 140 | } 141 | 142 | // WritePacket implements stack.LinkEndpoint.WritePacket. It delivers outbound 143 | // packets to the network-layer dispatcher. 144 | func (e *endpoint) WritePacket(s *stack.Route, _ *stack.GSO, hdr buffer.Prependable, payload buffer.VectorisedView, protocol tcpip.NetworkProtocolNumber) *tcpip.Error { 145 | views := make([]buffer.View, 1, 1+len(payload.Views())) 146 | views[0] = hdr.View() 147 | views = append(views, payload.Views()...) 148 | vv := buffer.NewVectorisedView(len(views[0])+payload.Size(), views) 149 | 150 | _, err := e.tagLink.Write(vv.ToView()) 151 | if err != nil { 152 | log.Printf("WritePacket: Error writing to link buffer, dropping packet: %v", err) 153 | log.Printf("WritePacket: Available Write Buffers: %d", e.tagLink.txBuffer.avail) 154 | e.stats.TxErrors++ 155 | return nil 156 | } 157 | e.stats.TxPackets++ 158 | return nil 159 | } 160 | -------------------------------------------------------------------------------- /link/aws/tag/tag_link.go: -------------------------------------------------------------------------------- 1 | package tag 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "strings" 7 | "sync" 8 | "time" 9 | 10 | "github.com/aws/aws-sdk-go/aws" 11 | "github.com/aws/aws-sdk-go/service/lambda" 12 | ) 13 | 14 | type FunctionTags map[string]string 15 | 16 | // TagStats captures data on packets sent or received in AWS Lambda tags 17 | type TagStats struct { 18 | RxErrors uint32 19 | TxErrors uint32 20 | AwsRequests uint32 21 | UpdatedTxTags uint32 22 | DeletedRxTags uint32 23 | } 24 | 25 | func (t FunctionTags) String() string { 26 | a := make([]string, 0) 27 | for k := range t { 28 | a = append(a, k) 29 | } 30 | return fmt.Sprintf("[%v]", strings.Join(a, ",")) 31 | } 32 | 33 | // TagLink reads/writes L2 data to AWS service(s) 34 | type TagLink struct { 35 | svc *lambda.Lambda 36 | txArn string 37 | rxArn string 38 | ep *endpoint 39 | rxBuffer *TagRing 40 | txBuffer *TagRing 41 | stats *TagStats 42 | mtu int 43 | // todo: look into implementing this with channels 44 | txHarvester *TagHarvester 45 | rxHarvester *TagHarvester 46 | txMux sync.Mutex 47 | rxMux sync.Mutex 48 | } 49 | 50 | // todo: can we just read in a bunch of packets at once? 51 | // https://github.com/google/netstack/blob/74ad0f9b269317db70f62402f7d4c51b2c7ca0b7/tcpip/link/fdbased/endpoint.go#L155 52 | // e.views = make([][]buffer.View, msgsPerRecv) 53 | 54 | // recieving multiple packets at once: 55 | // https://github.com/google/netstack/blob/74ad0f9b269317db70f62402f7d4c51b2c7ca0b7/tcpip/link/fdbased/endpoint.go#L330 56 | 57 | type TagConfig struct { 58 | LambdaService *lambda.Lambda 59 | Endpoint *endpoint 60 | RxArn string // local (receive lambda tags) 61 | TxArn string // remote (transmit lambda tags) 62 | } 63 | 64 | type TagHarvester struct { 65 | t *time.Ticker 66 | d time.Duration 67 | svc *lambda.Lambda 68 | arn string 69 | mux sync.Mutex 70 | tagHandler func(map[string]*string, error) 71 | err chan error 72 | } 73 | 74 | func NewTagHarvester(d time.Duration, svc *lambda.Lambda, arn string, mux sync.Mutex, tagHandler func(map[string]*string, error)) *TagHarvester { 75 | return &TagHarvester{ 76 | d: d, 77 | arn: arn, 78 | svc: svc, 79 | mux: mux, 80 | tagHandler: tagHandler, 81 | } 82 | } 83 | 84 | func (th *TagHarvester) Start() { 85 | th.t = time.NewTicker(th.d) 86 | go func() { 87 | for { 88 | select { 89 | case <-th.t.C: 90 | th.mux.Lock() 91 | tagsOutput, err := th.svc.ListTags(&lambda.ListTagsInput{Resource: aws.String(th.arn)}) 92 | if err != nil { 93 | th.tagHandler(nil, err) 94 | th.mux.Unlock() 95 | return 96 | } 97 | th.tagHandler(tagsOutput.Tags, nil) 98 | th.mux.Unlock() 99 | } 100 | } 101 | }() 102 | } 103 | 104 | func (th *TagHarvester) Stop() { 105 | th.t.Stop() 106 | } 107 | 108 | // todo: define how this maps to multi-tags (?) 109 | var BufConfig = []int{255, 255, 255, 255, 255, 255, 255, 255} 110 | 111 | const PollInterval = 500 * time.Millisecond 112 | 113 | func NewTagLink(config *TagConfig) *TagLink { 114 | tagLink := TagLink{mtu: 255, txArn: config.TxArn, rxArn: config.RxArn, svc: config.LambdaService, stats: &TagStats{}, ep: config.Endpoint} 115 | tagLink.txBuffer = NewTagRing(len(BufConfig), TransmitType) 116 | tagLink.rxBuffer = NewTagRing(len(BufConfig), ReceiveType) 117 | tagLink.txHarvester = NewTagHarvester(PollInterval, config.LambdaService, config.TxArn, tagLink.txMux, tagLink.refreshTxInternalBuffers) 118 | tagLink.rxHarvester = NewTagHarvester(PollInterval, config.LambdaService, config.RxArn, tagLink.rxMux, tagLink.refreshRxInternalBuffers) 119 | return &tagLink 120 | } 121 | 122 | func (t *TagLink) StartPolling() { 123 | t.txHarvester.Start() 124 | t.rxHarvester.Start() 125 | } 126 | 127 | // tagHandler 128 | func (t *TagLink) refreshTxInternalBuffers(tags map[string]*string, err error) { 129 | if err != nil { 130 | panic(err) 131 | } 132 | t.txBuffer.Reset() 133 | for i := 0; i < len(BufConfig); i++ { 134 | var buf []byte 135 | // Transmit tags 136 | if val, ok := tags[t.TxTagIndex(i)]; ok { 137 | buf = []byte(aws.StringValue(val)) 138 | } else { 139 | buf = make([]byte, 0) 140 | } 141 | if err := t.txBuffer.Replace(i, buf); err != nil { 142 | panic(err) 143 | } 144 | } 145 | 146 | return 147 | } 148 | 149 | func (t *TagLink) refreshRxInternalBuffers(tags map[string]*string, err error) { 150 | if err != nil { 151 | panic(err) 152 | } 153 | t.rxBuffer.Reset() 154 | for i := 0; i < len(BufConfig); i++ { 155 | var buf []byte 156 | // Receive tags 157 | if val, ok := tags[t.RxTagIndex(i)]; ok { 158 | buf = []byte(aws.StringValue(val)) 159 | } else { 160 | buf = make([]byte, 0) 161 | } 162 | if err := t.rxBuffer.Replace(i, buf); err != nil { 163 | panic(err) 164 | } 165 | } 166 | 167 | return 168 | } 169 | 170 | func (t *TagLink) LinkAddressLabel() string { 171 | return fmt.Sprintf("link:%s", t.ep.laddr) 172 | } 173 | 174 | func (t *TagLink) RemoteLinkAddressLabel() string { 175 | return fmt.Sprintf("link:%s", t.ep.raddr) 176 | } 177 | 178 | func (t *TagLink) RxTagIndex(i int) string { 179 | return fmt.Sprintf("%s.%d", t.LinkAddressLabel(), i) 180 | } 181 | 182 | func (t *TagLink) TxTagIndex(i int) string { 183 | return fmt.Sprintf("%s.%d", t.RemoteLinkAddressLabel(), i) 184 | } 185 | 186 | func (t *TagLink) String() string { 187 | var s []string 188 | for i := 0; i < len(BufConfig); i++ { 189 | if t.rxBuffer.Seek(i).b.Offset() > 0 { 190 | s = append(s, fmt.Sprintf("[%d] Rx Buffer: %v", i, t.rxBuffer.Seek(i))) 191 | } 192 | } 193 | for j := 0; j < len(BufConfig); j++ { 194 | if t.txBuffer.Seek(j).b.Len() > 0 { 195 | s = append(s, fmt.Sprintf("[%d] Tx Buffer: %v", j, t.txBuffer.Seek(j))) 196 | } 197 | } 198 | return strings.Join(s, "\n") 199 | } 200 | 201 | func (t *TagLink) FlushTransmit() (*string, error) { 202 | if t.txArn == "" { 203 | return nil, nil 204 | } 205 | 206 | updatedTags := make(FunctionTags) 207 | i := t.txBuffer.lastWriteOp 208 | 209 | if i == TagRingNoOp { 210 | return nil, nil 211 | } 212 | 213 | if t.txBuffer.Seek(i).b.Len() == 0 { 214 | panic("FlushTransmit: Unexpected flush of empty buffer") 215 | } 216 | 217 | updatedTags[t.TxTagIndex(i)] = t.txBuffer.Seek(i).b.EncodedBytesString() 218 | 219 | _, err := t.updateTags(updatedTags) 220 | if err != nil { 221 | return nil, err 222 | } 223 | t.stats.UpdatedTxTags++ 224 | return aws.String(t.TxTagIndex(i)), nil 225 | } 226 | 227 | func (t *TagLink) ReceivePacketLen() int { 228 | return t.rxBuffer.avail 229 | } 230 | 231 | func (t *TagLink) FlushReceive() (*string, error) { 232 | if t.rxArn == "" { 233 | return nil, nil 234 | } 235 | 236 | i := t.rxBuffer.lastReadOp 237 | buf := t.rxBuffer.Seek(i).b 238 | if i == TagRingNoOp { 239 | return nil, nil 240 | } 241 | if buf.Offset() == 0 { 242 | panic("FlushReceive: Unexpected flush of empty buffer") 243 | } 244 | 245 | buf.Reset() 246 | clearTag := t.RxTagIndex(i) 247 | _, err := t.removeTags([]string{clearTag}) 248 | if err != nil { 249 | return nil, err 250 | } 251 | t.stats.DeletedRxTags++ 252 | return aws.String(clearTag), nil 253 | } 254 | 255 | // TODO: make this a blocking read until new bytes are in 256 | 257 | // Read reads one packet from the internal buffers 258 | func (t *TagLink) Read(p []byte) (int, error) { 259 | t.rxMux.Lock() 260 | defer t.rxMux.Unlock() 261 | 262 | n, err := t.rxBuffer.Read(p) 263 | if n > 0 { 264 | _, err = t.FlushReceive() 265 | if err != nil { 266 | // TODO: how to recover from this 267 | return 0, err 268 | } 269 | } 270 | return n, err 271 | } 272 | 273 | // Write writes one packet to the internal buffers 274 | func (t *TagLink) Write(p []byte) (int, error) { 275 | t.txMux.Lock() 276 | defer t.txMux.Unlock() 277 | 278 | n, err := t.txBuffer.Write(p) 279 | if err != nil { 280 | return n, err 281 | } 282 | 283 | _, err = t.FlushTransmit() 284 | if err != nil { 285 | // TODO: how to recover from this (?) 286 | log.Printf("WritePacket: Error flushing to remote link, dropping packet: %v", err) 287 | return 0, err 288 | } 289 | 290 | return n, nil 291 | } 292 | 293 | func (t *TagLink) removeTags(tagKeys []string) (*lambda.UntagResourceOutput, error) { 294 | t.stats.AwsRequests++ 295 | tagInput := &lambda.UntagResourceInput{ 296 | Resource: aws.String(t.rxArn), 297 | TagKeys: aws.StringSlice(tagKeys), 298 | } 299 | output, err := t.svc.UntagResource(tagInput) 300 | if err != nil { 301 | return nil, err 302 | } 303 | return output, nil 304 | } 305 | 306 | func (t *TagLink) updateTags(tags FunctionTags) (*lambda.TagResourceOutput, error) { 307 | t.stats.AwsRequests++ 308 | tagInput := &lambda.TagResourceInput{ 309 | Resource: aws.String(t.txArn), 310 | Tags: aws.StringMap(tags), 311 | } 312 | output, err := t.svc.TagResource(tagInput) 313 | if err != nil { 314 | return nil, err 315 | } 316 | return output, nil 317 | } 318 | -------------------------------------------------------------------------------- /link/aws/tag/tag_link_test.go: -------------------------------------------------------------------------------- 1 | package tag 2 | 3 | import ( 4 | "io" 5 | "testing" 6 | 7 | "github.com/aws/aws-sdk-go/aws" 8 | "github.com/aws/aws-sdk-go/aws/session" 9 | "github.com/aws/aws-sdk-go/service/lambda" 10 | ) 11 | 12 | func setupTagLink(t *testing.T) *TagLink { 13 | sess, _ := session.NewSession(&aws.Config{ 14 | Region: aws.String("us-west-2")}, 15 | ) 16 | svc := lambda.New(sess, &aws.Config{Region: aws.String("us-west-2")}) 17 | endpoint := &endpoint{laddr: "ABC", raddr: "DEF"} 18 | config := TagConfig{ 19 | LambdaService: svc, 20 | Endpoint: endpoint, 21 | TxArn: "", 22 | RxArn: "", 23 | //Arn: "arn:aws:lambda:us-west-2:275197385476:function:helloWorldTestFunction", 24 | } 25 | return NewTagLink(&config) 26 | } 27 | 28 | func TestTagLink_Read(t *testing.T) { 29 | tl := setupTagLink(t) 30 | tables := []struct { 31 | read []byte 32 | readBytes int 33 | readError error 34 | availPackets int 35 | }{ 36 | {make([]byte, 255), 0, io.EOF, 0}, 37 | } 38 | for i, table := range tables { 39 | tl.rxBuffer.Reset() 40 | if tl.rxBuffer.avail != table.availPackets { 41 | t.Errorf("[%d] TestTagLink_Read: expected %d avail packets, got %d", i, table.availPackets, tl.rxBuffer.avail) 42 | } 43 | n, err := tl.Read(table.read) 44 | if err != table.readError { 45 | t.Errorf("[%d] TestTagLink_Read: Expected read error %v, got: %v", i, table.readError, err) 46 | } 47 | if n != table.readBytes { 48 | t.Errorf("[%d] TestTagLink_Read: Expected to read bytes %d, got: %d", i, table.readBytes, n) 49 | } 50 | } 51 | } 52 | 53 | func TestTagLink_Write(t *testing.T) { 54 | tl := setupTagLink(t) 55 | 56 | tables := []struct { 57 | write []byte 58 | writtenBytes int 59 | writeError error 60 | availPackets int 61 | }{ 62 | {make([]byte, 0), 0, nil, 8}, 63 | {[]byte("helloworld"), 16, nil, 7}, 64 | } 65 | for i, table := range tables { 66 | tl.txBuffer.Reset() 67 | n, err := tl.Write(table.write) 68 | 69 | if tl.txBuffer.avail != table.availPackets { 70 | t.Errorf("[%d] TestTagLink_Write: expected %d avail packets, got %d", i, table.availPackets, tl.txBuffer.avail) 71 | } 72 | 73 | if err != table.writeError { 74 | t.Errorf("[%d] Expected write error %v, got: %v", i, table.writeError, err) 75 | } 76 | if n != table.writtenBytes { 77 | t.Errorf("[%d] Expected to write bytes %d, got: %d", i, table.writtenBytes, n) 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## Richard Linklayer (rlinklayer) 2 | 3 | experimental userspace network overlay designed for serverless functions that tunnels through Amazon Cloudwatch Logs or AWS Lambda tags that uses Google's [netstack](https://github.com/google/netstack) library. 4 | 5 | You could use this library to: 6 | 7 | * Run non-privilged servers inside of AWS Lambda and access them over TCP/IP (slowly) 8 | * Establish a (slow) TCP or UDP connection between function(s) 9 | * Bridge a network running inside of AWS Lambda functions with a normal network. 10 | 11 | The current state of this project is proof-of-concept/experimental. This isn't meant for anything production. 12 | 13 | ### running on AWS Lambda 14 | 15 | Currently, this is only supported with a custom runtime. See `examples/template.yaml` for a sample function that runs an HTTP server in node.js. 16 | 17 | A publicly-deployable AWS Lambda Layer is also provided for running with your own functions. 18 | 19 | ### running locally on Mac OS X 20 | 21 | Special permissions are needed in Docker to create a tun or tap network interface. Because AWS Services are used as a link layer transport, AWS credentials are needed: 22 | 23 | First, build the docker image: 24 | 25 | ```sh 26 | docker build -t smithclay/rlinklayer . 27 | ``` 28 | 29 | Next, run the container with AWS credentials that can write and read to Amazon Cloudwatch Logs. 30 | 31 | The `start-server.sh` script automatically creates a tun or tap network interface in the docker container, if one does not exist. 32 | 33 | ```sh 34 | docker run --env AWS_ACCESS_KEY_ID=<> env AWS_SECRET_ACCESS_KEY=>--name richard-linklayer --privileged smithclay/rlinklayer ./start-server.sh 35 | ``` 36 | 37 | Then, run do networking stuff that interacts with an IP address that's a running function. 38 | 39 | ```sh 40 | docker exec -ti richard-linklayer ping 192.168.1.21 41 | ``` 42 | 43 | ### examples 44 | 45 | Examples are in the `examples` directory. 46 | 47 | -------------------------------------------------------------------------------- /start-server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | function ifup { 4 | if [[ ! -d /sys/class/net/${1} ]]; then 5 | printf '[bootstrap] No such interface: %s\n' "$1" >&2 6 | return 1 7 | else 8 | [[ $(