Upgrade vpnkit vendoring

Signed-off-by: Guillaume Rose <guillaume.rose@docker.com>
This commit is contained in:
Guillaume Rose 2020-03-05 15:19:47 +01:00
parent badd791072
commit 57bf9f39de
258 changed files with 8347 additions and 62867 deletions

View File

@ -14,6 +14,7 @@ import (
"github.com/google/uuid"
"github.com/moby/hyperkit/go"
"github.com/moby/vpnkit/go/pkg/vmnet"
"github.com/moby/vpnkit/go/pkg/vpnkit"
log "github.com/sirupsen/logrus"
)
@ -435,17 +436,17 @@ func vpnkitPublishPorts(h *hyperkit.HyperKit, publishFlags multipleFlag, portSoc
}
log.Debugf("Creating new VPNKit VMNet on %s", h.VPNKitSock)
vmnet, err := vpnkit.NewVmnet(ctx, h.VPNKitSock)
vmnetClient, err := vmnet.New(ctx, h.VPNKitSock)
if err != nil {
return nil, fmt.Errorf("NewVmnet failed: %v", err)
}
defer vmnet.Close()
defer vmnetClient.Close()
// Register with VPNKit
var vif *vpnkit.Vif
var vif *vmnet.Vif
if h.VPNKitPreferredIPv4 == "" {
log.Debugf("Creating VPNKit VIF for %v", vpnkitUUID)
vif, err = vmnet.ConnectVif(vpnkitUUID)
vif, err = vmnetClient.ConnectVif(vpnkitUUID)
if err != nil {
return nil, fmt.Errorf("Connection to Vif failed: %v", err)
}
@ -455,7 +456,7 @@ func vpnkitPublishPorts(h *hyperkit.HyperKit, publishFlags multipleFlag, portSoc
return nil, fmt.Errorf("Failed to parse IP: %s", h.VPNKitPreferredIPv4)
}
log.Debugf("Creating VPNKit VIF for %v ip=%v", vpnkitUUID, ip)
vif, err = vmnet.ConnectVifIP(vpnkitUUID, ip)
vif, err = vmnetClient.ConnectVifIP(vpnkitUUID, ip)
if err != nil {
return nil, fmt.Errorf("Connection to Vif with IP failed: %v", err)
}
@ -463,7 +464,7 @@ func vpnkitPublishPorts(h *hyperkit.HyperKit, publishFlags multipleFlag, portSoc
log.Debugf("VPNKit UUID:%s IP: %v", vpnkitUUID, vif.IP)
log.Debugf("Connecting to VPNKit on %s", portSocket)
c, err := vpnkit.NewConnection(context.Background(), portSocket)
c, err := vpnkit.NewClient(portSocket)
if err != nil {
return nil, fmt.Errorf("Connection to VPNKit failed: %v", err)
}
@ -477,8 +478,14 @@ func vpnkitPublishPorts(h *hyperkit.HyperKit, publishFlags multipleFlag, portSoc
}
log.Debugf("Publishing %s", publish)
vp := vpnkit.NewPort(c, p.Protocol, localhost, p.Host, vif.IP, p.Guest)
if err = vp.Expose(context.Background()); err != nil {
vp := &vpnkit.Port{
Proto: vpnkit.Protocol(p.Protocol),
OutIP: localhost,
OutPort: p.Host,
InIP: vif.IP,
InPort: p.Guest,
}
if err = c.Expose(context.Background(), vp); err != nil {
return nil, fmt.Errorf("Failed to expose port %s: %v", publish, err)
}
ports = append(ports, vp)
@ -487,7 +494,7 @@ func vpnkitPublishPorts(h *hyperkit.HyperKit, publishFlags multipleFlag, portSoc
// Return cleanup function
return func() {
for _, vp := range ports {
vp.Unexpose(context.Background())
c.Unexpose(context.Background(), vp)
}
}, nil
}

View File

@ -1,7 +1,10 @@
cloud.google.com/go v0.53.0
github.com/Azure/azure-sdk-for-go 26132835cbefa2669a306b777f34b929b56aa0a2
github.com/Azure/go-ansiterm d6e3b3328b783f23731bc4d058875b0371ff8109
github.com/Azure/go-autorest 58f6f26e200fa5dfb40c9cd1c83f3e2c860d779d
github.com/Microsoft/go-winio v0.4.8
github.com/Nvveen/Gotty a8b993ba6abdb0e0c12b0125c603323a71c7790c https://github.com/ijc25/Gotty
github.com/ScaleFT/sshkeys 82451a80368171b074c7129d43b47fc2773f6e9f
github.com/agl/ed25519 5312a61534124124185d41f09206b9fef1d88403
github.com/aws/aws-sdk-go fa107560b5f3528a859a1a1511086646731bb1a8
github.com/beorn7/perks 4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9
@ -19,13 +22,11 @@ github.com/docker/docker-credential-helpers 5241b46610f2491efdf9d1c85f1ddf5b02f6
github.com/docker/go d30aec9fd63c35133f8f79c3412ad91a3b08be06
github.com/docker/go-connections 7beb39f0b969b075d1325fecb092faf27fd357b6
github.com/docker/go-metrics d466d4f6fd960e01820085bd7e1a24426ee7ef18
github.com/docker/go-p9p 87ae8514a3a2d9684994a6c319f96ba9e18a062e
github.com/docker/go-units 9e638d38cf6977a37a8ea0078f3ee75a7cdb2dd1
github.com/go-ini/ini afbc45e87f3ba324c532d12c71918ef52e0fb194
github.com/gogo/protobuf v1.0.0
github.com/golang/protobuf v1.1.0
github.com/google/uuid 7e072fc3a7be179aee6d3359e46015aa8c995314
github.com/googleapis/gax-go 8c5154c0fe5bf18cf649634d4c6df50897a32751
github.com/gophercloud/gophercloud b9ea9cb68cf5803ea1567c404b549a783c8264b2
github.com/gophercloud/utils 34f5991525d116b3832e0d9409492274f1c06bda
github.com/gorilla/context v1.1
@ -33,13 +34,11 @@ github.com/gorilla/mux v1.1
github.com/gorilla/websocket 21ab95fa12b9bdd8fecf5fa3586aad941cc98785
github.com/inconshreveable/mousetrap 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
github.com/jmespath/go-jmespath bd40a432e4c76585ef6b72d3fd96fb9b6dc7b68d
github.com/Nvveen/Gotty a8b993ba6abdb0e0c12b0125c603323a71c7790c https://github.com/ijc25/Gotty
github.com/linuxkit/virtsock 8e79449dea0735c1c056d814934dd035734cc97c
github.com/matttproud/golang_protobuf_extensions v1.0.0
github.com/mitchellh/go-ps 4fdf99ab29366514c69ccccddab5dc58b8d84062
github.com/moby/datakit 97b3d230535397a813323902c23751e176481a86
github.com/moby/hyperkit d65b09c1c28a2bfb6a976c86ecd885d2ee4c71d3
github.com/moby/vpnkit 0e4293bb1058598c4b0a406ed171f52573ef414c
github.com/moul/anonuuid c6987e46f8a0231504bcd402962749f8dd9970b0
github.com/moby/vpnkit 2ffc1dd8a84ea7359dd09b1f4b51bb728d4f46a0
github.com/moul/gotty-client e5589f6df35953284b091b8394daa6be6c453469
github.com/opencontainers/go-digest v1.0.0-rc1
github.com/opencontainers/image-spec v1.0.1
@ -54,9 +53,7 @@ github.com/prometheus/common ebdfc6da46522d58825777cf1f90490a5b1ef1d8
github.com/prometheus/procfs abf152e5f3e97f2fafac028d2cc06c1feb87ffa5
github.com/radu-matei/azure-sdk-for-go 3b12823551999669c9a325a32472508e0af7978e
github.com/radu-matei/azure-vhd-utils e52754d5569d2a643a7775f72ff2a6cf524f4c25
github.com/renstrom/fuzzysearch 7a8f9a1c4bed53899ecd512daeaf8207cc454156
github.com/rn/iso9660wrap baf8d62ad3155152b488d5ff9d4f2b9bb0d6986a
github.com/ScaleFT/sshkeys 82451a80368171b074c7129d43b47fc2773f6e9f
github.com/scaleway/scaleway-sdk-go 20b731586975c078d9c2d7dd0002127e9e9cdef2
github.com/sirupsen/logrus v1.0.3
github.com/spf13/cobra v0.0.3
@ -73,8 +70,5 @@ golang.org/x/net 0ed95abb35c445290478a5348a7b38bb154135fd
golang.org/x/oauth2 1611bb46e67abc64a71ecc5c3ae67f1cbbc2b921
golang.org/x/sync fd80eb99c8f653c847d294a001bdf2a3a6f768f5
golang.org/x/sys 37707fdb30a5b38865cfb95e5aab41707daec7fd
golang.org/x/text f72d8390a633d5dfb0cc84043294db9f6c935756
google.golang.org/genproto 694d95ba50e67b2e363f3483057db5d4910c18f9
google.golang.org/api 373a4c220f5c90e5b7ff7101779c5be385d171be
google.golang.org/grpc v1.12.0
gopkg.in/yaml.v2 4c78c975fe7c825c6d1466c42be594d1d6f3aba6

View File

@ -187,7 +187,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2014 Google Inc.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,188 +1,76 @@
# Google Cloud for Go
# Google Cloud Client Libraries for Go
[![Build Status](https://travis-ci.org/GoogleCloudPlatform/google-cloud-go.svg?branch=master)](https://travis-ci.org/GoogleCloudPlatform/google-cloud-go)
[![GoDoc](https://godoc.org/cloud.google.com/go?status.svg)](https://godoc.org/cloud.google.com/go)
Go packages for [Google Cloud Platform](https://cloud.google.com) services.
``` go
import "cloud.google.com/go"
```
Go packages for Google Cloud Platform services.
To install the packages on your system, *do not clone the repo*. Instead:
To install the packages on your system,
1. Change to your project directory:
```
$ go get -u cloud.google.com/go/...
```
```
cd /my/cloud/project
```
1. Get the package you want to use. Some products have their own module, so it's
best to `go get` the package(s) you want to use:
**NOTE:** These packages are under development, and may occasionally make
backwards-incompatible changes.
```
$ go get cloud.google.com/go/firestore # Replace with the package you want to use.
```
**NOTE:** Some of these packages are under development, and may occasionally
make backwards-incompatible changes.
**NOTE:** Github repo is a mirror of [https://code.googlesource.com/gocloud](https://code.googlesource.com/gocloud).
* [News](#news)
* [Supported APIs](#supported-apis)
* [Go Versions Supported](#go-versions-supported)
* [Authorization](#authorization)
* [Cloud Datastore](#cloud-datastore-)
* [Cloud Storage](#cloud-storage-)
* [Cloud Pub/Sub](#cloud-pub-sub-)
* [Cloud BigQuery](#cloud-bigquery-)
* [Stackdriver Logging](#stackdriver-logging-)
* [Cloud Spanner](#cloud-spanner-)
## News
_February 14, 2017_
Release of a client library for Spanner. See
the
[blog post](https://cloudplatform.googleblog.com/2017/02/introducing-Cloud-Spanner-a-global-database-service-for-mission-critical-applications.html).
Note that although the Spanner service is beta, the Go client library is alpha.
_December 12, 2016_
Beta release of BigQuery, DataStore, Logging and Storage. See the
[blog post](https://cloudplatform.googleblog.com/2016/12/announcing-new-google-cloud-client.html).
Also, BigQuery now supports structs. Read a row directly into a struct with
`RowIterator.Next`, and upload a row directly from a struct with `Uploader.Put`.
You can also use field tags. See the [package documentation][cloud-bigquery-ref]
for details.
_December 5, 2016_
More changes to BigQuery:
* The `ValueList` type was removed. It is no longer necessary. Instead of
```go
var v ValueList
... it.Next(&v) ..
```
use
```go
var v []Value
... it.Next(&v) ...
```
* Previously, repeatedly calling `RowIterator.Next` on the same `[]Value` or
`ValueList` would append to the slice. Now each call resets the size to zero first.
* Schema inference will infer the SQL type BYTES for a struct field of
type []byte. Previously it inferred STRING.
* The types `uint`, `uint64` and `uintptr` are no longer supported in schema
inference. BigQuery's integer type is INT64, and those types may hold values
that are not correctly represented in a 64-bit signed integer.
* The SQL types DATE, TIME and DATETIME are now supported. They correspond to
the `Date`, `Time` and `DateTime` types in the new `cloud.google.com/go/civil`
package.
_November 17, 2016_
Change to BigQuery: values from INTEGER columns will now be returned as int64,
not int. This will avoid errors arising from large values on 32-bit systems.
_November 8, 2016_
New datastore feature: datastore now encodes your nested Go structs as Entity values,
instead of a flattened list of the embedded struct's fields.
This means that you may now have twice-nested slices, eg.
```go
type State struct {
Cities []struct{
Populations []int
}
}
```
See [the announcement](https://groups.google.com/forum/#!topic/google-api-go-announce/79jtrdeuJAg) for
more details.
_November 8, 2016_
Breaking changes to datastore: contexts no longer hold namespaces; instead you
must set a key's namespace explicitly. Also, key functions have been changed
and renamed.
* The WithNamespace function has been removed. To specify a namespace in a Query, use the Query.Namespace method:
```go
q := datastore.NewQuery("Kind").Namespace("ns")
```
* All the fields of Key are exported. That means you can construct any Key with a struct literal:
```go
k := &Key{Kind: "Kind", ID: 37, Namespace: "ns"}
```
* As a result of the above, the Key methods Kind, ID, d.Name, Parent, SetParent and Namespace have been removed.
* `NewIncompleteKey` has been removed, replaced by `IncompleteKey`. Replace
```go
NewIncompleteKey(ctx, kind, parent)
```
with
```go
IncompleteKey(kind, parent)
```
and if you do use namespaces, make sure you set the namespace on the returned key.
* `NewKey` has been removed, replaced by `NameKey` and `IDKey`. Replace
```go
NewKey(ctx, kind, name, 0, parent)
NewKey(ctx, kind, "", id, parent)
```
with
```go
NameKey(kind, name, parent)
IDKey(kind, id, parent)
```
and if you do use namespaces, make sure you set the namespace on the returned key.
* The `Done` variable has been removed. Replace `datastore.Done` with `iterator.Done`, from the package `google.golang.org/api/iterator`.
* The `Client.Close` method will have a return type of error. It will return the result of closing the underlying gRPC connection.
See [the announcement](https://groups.google.com/forum/#!topic/google-api-go-announce/hqXtM_4Ix-0) for
more details.
_October 27, 2016_
Breaking change to bigquery: `NewGCSReference` is now a function,
not a method on `Client`.
New bigquery feature: `Table.LoaderFrom` now accepts a `ReaderSource`, enabling
loading data into a table from a file or any `io.Reader`.
_October 21, 2016_
Breaking change to pubsub: removed `pubsub.Done`.
Use `iterator.Done` instead, where `iterator` is the package
`google.golang.org/api/iterator`.
[Older news](https://github.com/GoogleCloudPlatform/google-cloud-go/blob/master/old-news.md)
## Supported APIs
Google API | Status | Package
-------------------------------|--------------|-----------------------------------------------------------
[Datastore][cloud-datastore] | beta | [`cloud.google.com/go/datastore`][cloud-datastore-ref]
[Storage][cloud-storage] | beta | [`cloud.google.com/go/storage`][cloud-storage-ref]
[Bigtable][cloud-bigtable] | beta | [`cloud.google.com/go/bigtable`][cloud-bigtable-ref]
[BigQuery][cloud-bigquery] | beta | [`cloud.google.com/go/bigquery`][cloud-bigquery-ref]
[Logging][cloud-logging] | beta | [`cloud.google.com/go/logging`][cloud-logging-ref]
[Pub/Sub][cloud-pubsub] | alpha | [`cloud.google.com/go/pubsub`][cloud-pubsub-ref]
[Vision][cloud-vision] | beta | [`cloud.google.com/go/vision`][cloud-vision-ref]
[Language][cloud-language] | alpha | [`cloud.google.com/go/language/apiv1`][cloud-language-ref]
[Speech][cloud-speech] | alpha | [`cloud.google.com/go/speech/apiv1beta`][cloud-speech-ref]
[Spanner][cloud-spanner] | alpha | [`cloud.google.com/go/spanner`][cloud-spanner-ref]
Google API | Status | Package
------------------------------------------------|--------------|-----------------------------------------------------------
[Asset][cloud-asset] | alpha | [`cloud.google.com/go/asset/v1beta`](https://godoc.org/cloud.google.com/go/asset/v1beta)
[Automl][cloud-automl] | stable | [`cloud.google.com/go/automl/apiv1`](https://godoc.org/cloud.google.com/go/automl/apiv1)
[BigQuery][cloud-bigquery] | stable | [`cloud.google.com/go/bigquery`](https://godoc.org/cloud.google.com/go/bigquery)
[Bigtable][cloud-bigtable] | stable | [`cloud.google.com/go/bigtable`](https://godoc.org/cloud.google.com/go/bigtable)
[Cloudbuild][cloud-build] | stable | [`cloud.google.com/go/cloudbuild/apiv1`](https://godoc.org/cloud.google.com/go/cloudbuild/apiv1)
[Cloudtasks][cloud-tasks] | stable | [`cloud.google.com/go/cloudtasks/apiv2`](https://godoc.org/cloud.google.com/go/cloudtasks/apiv2)
[Container][cloud-container] | stable | [`cloud.google.com/go/container/apiv1`](https://godoc.org/cloud.google.com/go/container/apiv1)
[ContainerAnalysis][cloud-containeranalysis] | beta | [`cloud.google.com/go/containeranalysis/apiv1`](https://godoc.org/cloud.google.com/go/containeranalysis/apiv1)
[Dataproc][cloud-dataproc] | stable | [`cloud.google.com/go/dataproc/apiv1`](https://godoc.org/cloud.google.com/go/dataproc/apiv1)
[Datastore][cloud-datastore] | stable | [`cloud.google.com/go/datastore`](https://godoc.org/cloud.google.com/go/datastore)
[Debugger][cloud-debugger] | stable | [`cloud.google.com/go/debugger/apiv2`](https://godoc.org/cloud.google.com/go/debugger/apiv2)
[Dialogflow][cloud-dialogflow] | stable | [`cloud.google.com/go/dialogflow/apiv2`](https://godoc.org/cloud.google.com/go/dialogflow/apiv2)
[Data Loss Prevention][cloud-dlp] | stable | [`cloud.google.com/go/dlp/apiv2`](https://godoc.org/cloud.google.com/go/dlp/apiv2)
[ErrorReporting][cloud-errors] | alpha | [`cloud.google.com/go/errorreporting`](https://godoc.org/cloud.google.com/go/errorreporting)
[Firestore][cloud-firestore] | stable | [`cloud.google.com/go/firestore`](https://godoc.org/cloud.google.com/go/firestore)
[IAM][cloud-iam] | stable | [`cloud.google.com/go/iam`](https://godoc.org/cloud.google.com/go/iam)
[IoT][cloud-iot] | stable | [`cloud.google.com/go/iot/apiv1`](https://godoc.org/cloud.google.com/go/iot/apiv1)
[IRM][cloud-irm] | alpha | [`cloud.google.com/go/irm/apiv1alpha2`](https://godoc.org/cloud.google.com/go/irm/apiv1alpha2)
[KMS][cloud-kms] | stable | [`cloud.google.com/go/kms/apiv1`](https://godoc.org/cloud.google.com/go/kms/apiv1)
[Natural Language][cloud-natural-language] | stable | [`cloud.google.com/go/language/apiv1`](https://godoc.org/cloud.google.com/go/language/apiv1)
[Logging][cloud-logging] | stable | [`cloud.google.com/go/logging`](https://godoc.org/cloud.google.com/go/logging)
[Memorystore][cloud-memorystore] | alpha | [`cloud.google.com/go/redis/apiv1`](https://godoc.org/cloud.google.com/go/redis/apiv1)
[Monitoring][cloud-monitoring] | alpha | [`cloud.google.com/go/monitoring/apiv3`](https://godoc.org/cloud.google.com/go/monitoring/apiv3)
[OS Login][cloud-oslogin] | alpha | [`cloud.google.com/go/oslogin/apiv1`](https://godoc.org/cloud.google.com/go/oslogin/apiv1)
[Pub/Sub][cloud-pubsub] | stable | [`cloud.google.com/go/pubsub`](https://godoc.org/cloud.google.com/go/pubsub)
[Phishing Protection][cloud-phishingprotection] | alpha | [`cloud.google.com/go/phishingprotection/apiv1beta1`](https://godoc.org/cloud.google.com/go/phishingprotection/apiv1beta1)
[reCAPTCHA Enterprise][cloud-recaptcha] | alpha | [`cloud.google.com/go/recaptchaenterprise/apiv1beta1`](https://godoc.org/cloud.google.com/go/recaptchaenterprise/apiv1beta1)
[Recommender][cloud-recommender] | beta | [`cloud.google.com/go/recommender/apiv1beta1`](https://godoc.org/cloud.google.com/go/recommender/apiv1beta1)
[Scheduler][cloud-scheduler] | stable | [`cloud.google.com/go/scheduler/apiv1`](https://godoc.org/cloud.google.com/go/scheduler/apiv1)
[Securitycenter][cloud-securitycenter] | alpha | [`cloud.google.com/go/securitycenter/apiv1`](https://godoc.org/cloud.google.com/go/securitycenter/apiv1)
[Spanner][cloud-spanner] | stable | [`cloud.google.com/go/spanner`](https://godoc.org/cloud.google.com/go/spanner)
[Speech][cloud-speech] | stable | [`cloud.google.com/go/speech/apiv1`](https://godoc.org/cloud.google.com/go/speech/apiv1)
[Storage][cloud-storage] | stable | [`cloud.google.com/go/storage`](https://godoc.org/cloud.google.com/go/storage)
[Talent][cloud-talent] | alpha | [`cloud.google.com/go/talent/apiv4beta1`](https://godoc.org/cloud.google.com/go/talent/apiv4beta1)
[Text To Speech][cloud-texttospeech] | alpha | [`cloud.google.com/go/texttospeech/apiv1`](https://godoc.org/cloud.google.com/go/texttospeech/apiv1)
[Trace][cloud-trace] | alpha | [`cloud.google.com/go/trace/apiv2`](https://godoc.org/cloud.google.com/go/trace/apiv2)
[Translate][cloud-translate] | stable | [`cloud.google.com/go/translate`](https://godoc.org/cloud.google.com/go/translate)
[Video Intelligence][cloud-video] | alpha | [`cloud.google.com/go/videointelligence/apiv1beta1`](https://godoc.org/cloud.google.com/go/videointelligence/apiv1beta1)
[Vision][cloud-vision] | stable | [`cloud.google.com/go/vision/apiv1`](https://godoc.org/cloud.google.com/go/vision/apiv1)
[Webrisk][cloud-webrisk] | alpha | [`cloud.google.com/go/webrisk/apiv1beta1`](https://godoc.org/cloud.google.com/go/webrisk/apiv1beta1)
> **Alpha status**: the API is still being actively developed. As a
> result, it might change in backward-incompatible ways and is not recommended
@ -195,26 +83,20 @@ Google API | Status | Package
> **Stable status**: the API is mature and ready for production use. We will
> continue addressing bugs and feature requests.
Documentation and examples are available at
https://godoc.org/cloud.google.com/go
Visit or join the
[google-api-go-announce group](https://groups.google.com/forum/#!forum/google-api-go-announce)
for updates on these packages.
Documentation and examples are available at [godoc.org/cloud.google.com/go](https://godoc.org/cloud.google.com/go)
## Go Versions Supported
We support the two most recent major versions of Go. If Google App Engine uses
an older version, we support that as well. You can see which versions are
currently supported by looking at the lines following `go:` in
[`.travis.yml`](.travis.yml).
an older version, we support that as well.
## Authorization
By default, each API will use [Google Application Default Credentials][default-creds]
By default, each API will use [Google Application Default Credentials](https://developers.google.com/identity/protocols/application-default-credentials)
for authorization credentials used in calling the API endpoints. This will allow your
application to run in many environments without requiring explicit configuration.
[snip]:# (auth)
```go
client, err := storage.NewClient(ctx)
```
@ -222,11 +104,12 @@ client, err := storage.NewClient(ctx)
To authorize using a
[JSON key file](https://cloud.google.com/iam/docs/managing-service-account-keys),
pass
[`option.WithServiceAccountFile`](https://godoc.org/google.golang.org/api/option#WithServiceAccountFile)
[`option.WithCredentialsFile`](https://godoc.org/google.golang.org/api/option#WithCredentialsFile)
to the `NewClient` function of the desired package. For example:
[snip]:# (auth-JSON)
```go
client, err := storage.NewClient(ctx, option.WithServiceAccountFile("path/to/keyfile.json"))
client, err := storage.NewClient(ctx, option.WithCredentialsFile("path/to/keyfile.json"))
```
You can exert more control over authorization by using the
@ -234,247 +117,12 @@ You can exert more control over authorization by using the
create an `oauth2.TokenSource`. Then pass
[`option.WithTokenSource`](https://godoc.org/google.golang.org/api/option#WithTokenSource)
to the `NewClient` function:
[snip]:# (auth-ts)
```go
tokenSource := ...
client, err := storage.NewClient(ctx, option.WithTokenSource(tokenSource))
```
## Cloud Datastore [![GoDoc](https://godoc.org/cloud.google.com/go/datastore?status.svg)](https://godoc.org/cloud.google.com/go/datastore)
- [About Cloud Datastore][cloud-datastore]
- [Activating the API for your project][cloud-datastore-activation]
- [API documentation][cloud-datastore-docs]
- [Go client documentation](https://godoc.org/cloud.google.com/go/datastore)
- [Complete sample program](https://github.com/GoogleCloudPlatform/golang-samples/tree/master/datastore/tasks)
### Example Usage
First create a `datastore.Client` to use throughout your application:
```go
client, err := datastore.NewClient(ctx, "my-project-id")
if err != nil {
log.Fatal(err)
}
```
Then use that client to interact with the API:
```go
type Post struct {
Title string
Body string `datastore:",noindex"`
PublishedAt time.Time
}
keys := []*datastore.Key{
datastore.NewKey(ctx, "Post", "post1", 0, nil),
datastore.NewKey(ctx, "Post", "post2", 0, nil),
}
posts := []*Post{
{Title: "Post 1", Body: "...", PublishedAt: time.Now()},
{Title: "Post 2", Body: "...", PublishedAt: time.Now()},
}
if _, err := client.PutMulti(ctx, keys, posts); err != nil {
log.Fatal(err)
}
```
## Cloud Storage [![GoDoc](https://godoc.org/cloud.google.com/go/storage?status.svg)](https://godoc.org/cloud.google.com/go/storage)
- [About Cloud Storage][cloud-storage]
- [API documentation][cloud-storage-docs]
- [Go client documentation](https://godoc.org/cloud.google.com/go/storage)
- [Complete sample programs](https://github.com/GoogleCloudPlatform/golang-samples/tree/master/storage)
### Example Usage
First create a `storage.Client` to use throughout your application:
```go
client, err := storage.NewClient(ctx)
if err != nil {
log.Fatal(err)
}
```
```go
// Read the object1 from bucket.
rc, err := client.Bucket("bucket").Object("object1").NewReader(ctx)
if err != nil {
log.Fatal(err)
}
defer rc.Close()
body, err := ioutil.ReadAll(rc)
if err != nil {
log.Fatal(err)
}
```
## Cloud Pub/Sub [![GoDoc](https://godoc.org/cloud.google.com/go/pubsub?status.svg)](https://godoc.org/cloud.google.com/go/pubsub)
- [About Cloud Pubsub][cloud-pubsub]
- [API documentation][cloud-pubsub-docs]
- [Go client documentation](https://godoc.org/cloud.google.com/go/pubsub)
- [Complete sample programs](https://github.com/GoogleCloudPlatform/golang-samples/tree/master/pubsub)
### Example Usage
First create a `pubsub.Client` to use throughout your application:
```go
client, err := pubsub.NewClient(ctx, "project-id")
if err != nil {
log.Fatal(err)
}
```
Then use the client to publish and subscribe:
```go
// Publish "hello world" on topic1.
topic := client.Topic("topic1")
msgIDs, err := topic.Publish(ctx, &pubsub.Message{
Data: []byte("hello world"),
})
if err != nil {
log.Fatal(err)
}
// Create an iterator to pull messages via subscription1.
it, err := client.Subscription("subscription1").Pull(ctx)
if err != nil {
log.Println(err)
}
defer it.Stop()
// Consume N messages from the iterator.
for i := 0; i < N; i++ {
msg, err := it.Next()
if err == iterator.Done {
break
}
if err != nil {
log.Fatalf("Failed to retrieve message: %v", err)
}
fmt.Printf("Message %d: %s\n", i, msg.Data)
msg.Done(true) // Acknowledge that we've consumed the message.
}
```
## Cloud BigQuery [![GoDoc](https://godoc.org/cloud.google.com/go/bigquery?status.svg)](https://godoc.org/cloud.google.com/go/bigquery)
- [About Cloud BigQuery][cloud-bigquery]
- [API documentation][cloud-bigquery-docs]
- [Go client documentation][cloud-bigquery-ref]
- [Complete sample programs](https://github.com/GoogleCloudPlatform/golang-samples/tree/master/bigquery)
### Example Usage
First create a `bigquery.Client` to use throughout your application:
```go
c, err := bigquery.NewClient(ctx, "my-project-ID")
if err != nil {
// TODO: Handle error.
}
```
Then use that client to interact with the API:
```go
// Construct a query.
q := c.Query(`
SELECT year, SUM(number)
FROM [bigquery-public-data:usa_names.usa_1910_2013]
WHERE name = "William"
GROUP BY year
ORDER BY year
`)
// Execute the query.
it, err := q.Read(ctx)
if err != nil {
// TODO: Handle error.
}
// Iterate through the results.
for {
var values []bigquery.Value
err := it.Next(&values)
if err == iterator.Done {
break
}
if err != nil {
// TODO: Handle error.
}
fmt.Println(values)
}
```
## Stackdriver Logging [![GoDoc](https://godoc.org/cloud.google.com/go/logging?status.svg)](https://godoc.org/cloud.google.com/go/logging)
- [About Stackdriver Logging][cloud-logging]
- [API documentation][cloud-logging-docs]
- [Go client documentation][cloud-logging-ref]
- [Complete sample programs](https://github.com/GoogleCloudPlatform/golang-samples/tree/master/logging)
### Example Usage
First create a `logging.Client` to use throughout your application:
```go
ctx := context.Background()
client, err := logging.NewClient(ctx, "my-project")
if err != nil {
// TODO: Handle error.
}
```
Usually, you'll want to add log entries to a buffer to be periodically flushed
(automatically and asynchronously) to the Stackdriver Logging service.
```go
logger := client.Logger("my-log")
logger.Log(logging.Entry{Payload: "something happened!"})
```
Close your client before your program exits, to flush any buffered log entries.
```go
err = client.Close()
if err != nil {
// TODO: Handle error.
}
```
## Cloud Spanner [![GoDoc](https://godoc.org/cloud.google.com/go/spanner?status.svg)](https://godoc.org/cloud.google.com/go/spanner)
- [About Cloud Spanner][cloud-spanner]
- [API documentation][cloud-spanner-docs]
- [Go client documentation](https://godoc.org/cloud.google.com/go/spanner)
### Example Usage
First create a `spanner.Client` to use throughout your application:
```go
client, err := spanner.NewClient(ctx, "projects/P/instances/I/databases/D")
if err != nil {
log.Fatal(err)
}
```
```go
// Simple Reads And Writes
_, err := client.Apply(ctx, []*spanner.Mutation{
spanner.Insert("Users",
[]string{"name", "email"},
[]interface{}{"alice", "a@example.com"})})
if err != nil {
log.Fatal(err)
}
row, err := client.Single().ReadRow(ctx, "Users",
spanner.Key{"alice"}, []string{"email"})
if err != nil {
log.Fatal(err)
}
```
## Contributing
Contributions are welcome. Please, see the
@ -487,42 +135,45 @@ By participating in this project you agree to abide by its terms.
See [Contributor Code of Conduct](https://github.com/GoogleCloudPlatform/google-cloud-go/blob/master/CONTRIBUTING.md#contributor-code-of-conduct)
for more information.
[cloud-datastore]: https://cloud.google.com/datastore/
[cloud-datastore-ref]: https://godoc.org/cloud.google.com/go/datastore
[cloud-datastore-docs]: https://cloud.google.com/datastore/docs
[cloud-datastore-activation]: https://cloud.google.com/datastore/docs/activate
[cloud-pubsub]: https://cloud.google.com/pubsub/
[cloud-pubsub-ref]: https://godoc.org/cloud.google.com/go/pubsub
[cloud-pubsub-docs]: https://cloud.google.com/pubsub/docs
[cloud-storage]: https://cloud.google.com/storage/
[cloud-storage-ref]: https://godoc.org/cloud.google.com/go/storage
[cloud-storage-docs]: https://cloud.google.com/storage/docs
[cloud-storage-create-bucket]: https://cloud.google.com/storage/docs/cloud-console#_creatingbuckets
[cloud-bigtable]: https://cloud.google.com/bigtable/
[cloud-bigtable-ref]: https://godoc.org/cloud.google.com/go/bigtable
[cloud-asset]: https://cloud.google.com/security-command-center/docs/how-to-asset-inventory
[cloud-automl]: https://cloud.google.com/automl
[cloud-build]: https://cloud.google.com/cloud-build/
[cloud-bigquery]: https://cloud.google.com/bigquery/
[cloud-bigquery-docs]: https://cloud.google.com/bigquery/docs
[cloud-bigquery-ref]: https://godoc.org/cloud.google.com/go/bigquery
[cloud-logging]: https://cloud.google.com/logging/
[cloud-logging-docs]: https://cloud.google.com/logging/docs
[cloud-logging-ref]: https://godoc.org/cloud.google.com/go/logging
[cloud-vision]: https://cloud.google.com/vision/
[cloud-vision-ref]: https://godoc.org/cloud.google.com/go/vision
[cloud-bigtable]: https://cloud.google.com/bigtable/
[cloud-container]: https://cloud.google.com/containers/
[cloud-containeranalysis]: https://cloud.google.com/container-registry/docs/container-analysis
[cloud-dataproc]: https://cloud.google.com/dataproc/
[cloud-datastore]: https://cloud.google.com/datastore/
[cloud-dialogflow]: https://cloud.google.com/dialogflow-enterprise/
[cloud-debugger]: https://cloud.google.com/debugger/
[cloud-dlp]: https://cloud.google.com/dlp/
[cloud-errors]: https://cloud.google.com/error-reporting/
[cloud-firestore]: https://cloud.google.com/firestore/
[cloud-iam]: https://cloud.google.com/iam/
[cloud-iot]: https://cloud.google.com/iot-core/
[cloud-irm]: https://cloud.google.com/incident-response/docs/concepts
[cloud-kms]: https://cloud.google.com/kms/
[cloud-pubsub]: https://cloud.google.com/pubsub/
[cloud-storage]: https://cloud.google.com/storage/
[cloud-language]: https://cloud.google.com/natural-language
[cloud-language-ref]: https://godoc.org/cloud.google.com/go/language/apiv1
[cloud-speech]: https://cloud.google.com/speech
[cloud-speech-ref]: https://godoc.org/cloud.google.com/go/speech/apiv1beta1
[cloud-logging]: https://cloud.google.com/logging/
[cloud-natural-language]: https://cloud.google.com/natural-language/
[cloud-memorystore]: https://cloud.google.com/memorystore/
[cloud-monitoring]: https://cloud.google.com/monitoring/
[cloud-oslogin]: https://cloud.google.com/compute/docs/oslogin/rest
[cloud-phishingprotection]: https://cloud.google.com/phishing-protection/
[cloud-securitycenter]: https://cloud.google.com/security-command-center/
[cloud-scheduler]: https://cloud.google.com/scheduler
[cloud-spanner]: https://cloud.google.com/spanner/
[cloud-spanner-ref]: https://godoc.org/cloud.google.com/go/spanner
[cloud-spanner-docs]: https://cloud.google.com/spanner/docs
[default-creds]: https://developers.google.com/identity/protocols/application-default-credentials
[cloud-speech]: https://cloud.google.com/speech
[cloud-talent]: https://cloud.google.com/solutions/talent-solution/
[cloud-tasks]: https://cloud.google.com/tasks/
[cloud-texttospeech]: https://cloud.google.com/texttospeech/
[cloud-talent]: https://cloud.google.com/solutions/talent-solution/
[cloud-trace]: https://cloud.google.com/trace/
[cloud-translate]: https://cloud.google.com/translate
[cloud-recaptcha]: https://cloud.google.com/recaptcha-enterprise/
[cloud-recommender]: https://cloud.google.com/recommendations/
[cloud-video]: https://cloud.google.com/video-intelligence/
[cloud-vision]: https://cloud.google.com/vision
[cloud-webrisk]: https://cloud.google.com/web-risk/

View File

@ -0,0 +1,9 @@
// +build ignore
// Empty include file to generate z symbols
// EOF

View File

@ -0,0 +1,472 @@
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/*
* Line tables
*/
package gosym
import (
"encoding/binary"
"sync"
)
// A LineTable is a data structure mapping program counters to line numbers.
//
// In Go 1.1 and earlier, each function (represented by a Func) had its own LineTable,
// and the line number corresponded to a numbering of all source lines in the
// program, across all files. That absolute line number would then have to be
// converted separately to a file name and line number within the file.
//
// In Go 1.2, the format of the data changed so that there is a single LineTable
// for the entire program, shared by all Funcs, and there are no absolute line
// numbers, just line numbers within specific files.
//
// For the most part, LineTable's methods should be treated as an internal
// detail of the package; callers should use the methods on Table instead.
type LineTable struct {
Data []byte
PC uint64
Line int
// Go 1.2 state
mu sync.Mutex
go12 int // is this in Go 1.2 format? -1 no, 0 unknown, 1 yes
binary binary.ByteOrder
quantum uint32
ptrsize uint32
functab []byte
nfunctab uint32
filetab []byte
nfiletab uint32
fileMap map[string]uint32
}
// NOTE(rsc): This is wrong for GOARCH=arm, which uses a quantum of 4,
// but we have no idea whether we're using arm or not. This only
// matters in the old (pre-Go 1.2) symbol table format, so it's not worth
// fixing.
const oldQuantum = 1
func (t *LineTable) parse(targetPC uint64, targetLine int) (b []byte, pc uint64, line int) {
// The PC/line table can be thought of as a sequence of
// <pc update>* <line update>
// batches. Each update batch results in a (pc, line) pair,
// where line applies to every PC from pc up to but not
// including the pc of the next pair.
//
// Here we process each update individually, which simplifies
// the code, but makes the corner cases more confusing.
b, pc, line = t.Data, t.PC, t.Line
for pc <= targetPC && line != targetLine && len(b) > 0 {
code := b[0]
b = b[1:]
switch {
case code == 0:
if len(b) < 4 {
b = b[0:0]
break
}
val := binary.BigEndian.Uint32(b)
b = b[4:]
line += int(val)
case code <= 64:
line += int(code)
case code <= 128:
line -= int(code - 64)
default:
pc += oldQuantum * uint64(code-128)
continue
}
pc += oldQuantum
}
return b, pc, line
}
func (t *LineTable) slice(pc uint64) *LineTable {
data, pc, line := t.parse(pc, -1)
return &LineTable{Data: data, PC: pc, Line: line}
}
// PCToLine returns the line number for the given program counter.
// Callers should use Table's PCToLine method instead.
func (t *LineTable) PCToLine(pc uint64) int {
if t.isGo12() {
return t.go12PCToLine(pc)
}
_, _, line := t.parse(pc, -1)
return line
}
// LineToPC returns the program counter for the given line number,
// considering only program counters before maxpc.
// Callers should use Table's LineToPC method instead.
func (t *LineTable) LineToPC(line int, maxpc uint64) uint64 {
if t.isGo12() {
return 0
}
_, pc, line1 := t.parse(maxpc, line)
if line1 != line {
return 0
}
// Subtract quantum from PC to account for post-line increment
return pc - oldQuantum
}
// NewLineTable returns a new PC/line table
// corresponding to the encoded data.
// Text must be the start address of the
// corresponding text segment.
func NewLineTable(data []byte, text uint64) *LineTable {
return &LineTable{Data: data, PC: text, Line: 0}
}
// Go 1.2 symbol table format.
// See golang.org/s/go12symtab.
//
// A general note about the methods here: rather than try to avoid
// index out of bounds errors, we trust Go to detect them, and then
// we recover from the panics and treat them as indicative of a malformed
// or incomplete table.
//
// The methods called by symtab.go, which begin with "go12" prefixes,
// are expected to have that recovery logic.
// isGo12 reports whether this is a Go 1.2 (or later) symbol table.
func (t *LineTable) isGo12() bool {
t.go12Init()
return t.go12 == 1
}
const go12magic = 0xfffffffb
// uintptr returns the pointer-sized value encoded at b.
// The pointer size is dictated by the table being read.
func (t *LineTable) uintptr(b []byte) uint64 {
if t.ptrsize == 4 {
return uint64(t.binary.Uint32(b))
}
return t.binary.Uint64(b)
}
// go12init initializes the Go 1.2 metadata if t is a Go 1.2 symbol table.
func (t *LineTable) go12Init() {
t.mu.Lock()
defer t.mu.Unlock()
if t.go12 != 0 {
return
}
defer func() {
// If we panic parsing, assume it's not a Go 1.2 symbol table.
recover()
}()
// Check header: 4-byte magic, two zeros, pc quantum, pointer size.
t.go12 = -1 // not Go 1.2 until proven otherwise
if len(t.Data) < 16 || t.Data[4] != 0 || t.Data[5] != 0 ||
(t.Data[6] != 1 && t.Data[6] != 4) || // pc quantum
(t.Data[7] != 4 && t.Data[7] != 8) { // pointer size
return
}
switch uint32(go12magic) {
case binary.LittleEndian.Uint32(t.Data):
t.binary = binary.LittleEndian
case binary.BigEndian.Uint32(t.Data):
t.binary = binary.BigEndian
default:
return
}
t.quantum = uint32(t.Data[6])
t.ptrsize = uint32(t.Data[7])
t.nfunctab = uint32(t.uintptr(t.Data[8:]))
t.functab = t.Data[8+t.ptrsize:]
functabsize := t.nfunctab*2*t.ptrsize + t.ptrsize
fileoff := t.binary.Uint32(t.functab[functabsize:])
t.functab = t.functab[:functabsize]
t.filetab = t.Data[fileoff:]
t.nfiletab = t.binary.Uint32(t.filetab)
t.filetab = t.filetab[:t.nfiletab*4]
t.go12 = 1 // so far so good
}
// go12Funcs returns a slice of Funcs derived from the Go 1.2 pcln table.
func (t *LineTable) go12Funcs() []Func {
// Assume it is malformed and return nil on error.
defer func() {
recover()
}()
n := len(t.functab) / int(t.ptrsize) / 2
funcs := make([]Func, n)
for i := range funcs {
f := &funcs[i]
f.Entry = uint64(t.uintptr(t.functab[2*i*int(t.ptrsize):]))
f.End = uint64(t.uintptr(t.functab[(2*i+2)*int(t.ptrsize):]))
info := t.Data[t.uintptr(t.functab[(2*i+1)*int(t.ptrsize):]):]
f.LineTable = t
f.FrameSize = int(t.binary.Uint32(info[t.ptrsize+2*4:]))
f.Sym = &Sym{
Value: f.Entry,
Type: 'T',
Name: t.string(t.binary.Uint32(info[t.ptrsize:])),
GoType: 0,
Func: f,
}
}
return funcs
}
// findFunc returns the func corresponding to the given program counter.
func (t *LineTable) findFunc(pc uint64) []byte {
if pc < t.uintptr(t.functab) || pc >= t.uintptr(t.functab[len(t.functab)-int(t.ptrsize):]) {
return nil
}
// The function table is a list of 2*nfunctab+1 uintptrs,
// alternating program counters and offsets to func structures.
f := t.functab
nf := t.nfunctab
for nf > 0 {
m := nf / 2
fm := f[2*t.ptrsize*m:]
if t.uintptr(fm) <= pc && pc < t.uintptr(fm[2*t.ptrsize:]) {
return t.Data[t.uintptr(fm[t.ptrsize:]):]
} else if pc < t.uintptr(fm) {
nf = m
} else {
f = f[(m+1)*2*t.ptrsize:]
nf -= m + 1
}
}
return nil
}
// readvarint reads, removes, and returns a varint from *pp.
func (t *LineTable) readvarint(pp *[]byte) uint32 {
var v, shift uint32
p := *pp
for shift = 0; ; shift += 7 {
b := p[0]
p = p[1:]
v |= (uint32(b) & 0x7F) << shift
if b&0x80 == 0 {
break
}
}
*pp = p
return v
}
// string returns a Go string found at off.
func (t *LineTable) string(off uint32) string {
for i := off; ; i++ {
if t.Data[i] == 0 {
return string(t.Data[off:i])
}
}
}
// step advances to the next pc, value pair in the encoded table.
func (t *LineTable) step(p *[]byte, pc *uint64, val *int32, first bool) bool {
uvdelta := t.readvarint(p)
if uvdelta == 0 && !first {
return false
}
if uvdelta&1 != 0 {
uvdelta = ^(uvdelta >> 1)
} else {
uvdelta >>= 1
}
vdelta := int32(uvdelta)
pcdelta := t.readvarint(p) * t.quantum
*pc += uint64(pcdelta)
*val += vdelta
return true
}
// pcvalue reports the value associated with the target pc.
// off is the offset to the beginning of the pc-value table,
// and entry is the start PC for the corresponding function.
func (t *LineTable) pcvalue(off uint32, entry, targetpc uint64) int32 {
if off == 0 {
return -1
}
p := t.Data[off:]
val := int32(-1)
pc := entry
for t.step(&p, &pc, &val, pc == entry) {
if targetpc < pc {
return val
}
}
return -1
}
// findFileLine scans one function in the binary looking for a
// program counter in the given file on the given line.
// It does so by running the pc-value tables mapping program counter
// to file number. Since most functions come from a single file, these
// are usually short and quick to scan. If a file match is found, then the
// code goes to the expense of looking for a simultaneous line number match.
func (t *LineTable) findFileLine(entry uint64, filetab, linetab uint32, filenum, line int32) uint64 {
if filetab == 0 || linetab == 0 {
return 0
}
fp := t.Data[filetab:]
fl := t.Data[linetab:]
fileVal := int32(-1)
filePC := entry
lineVal := int32(-1)
linePC := entry
fileStartPC := filePC
for t.step(&fp, &filePC, &fileVal, filePC == entry) {
if fileVal == filenum && fileStartPC < filePC {
// fileVal is in effect starting at fileStartPC up to
// but not including filePC, and it's the file we want.
// Run the PC table looking for a matching line number
// or until we reach filePC.
lineStartPC := linePC
for linePC < filePC && t.step(&fl, &linePC, &lineVal, linePC == entry) {
// lineVal is in effect until linePC, and lineStartPC < filePC.
if lineVal == line {
if fileStartPC <= lineStartPC {
return lineStartPC
}
if fileStartPC < linePC {
return fileStartPC
}
}
lineStartPC = linePC
}
}
fileStartPC = filePC
}
return 0
}
// go12PCToLine maps program counter to line number for the Go 1.2 pcln table.
func (t *LineTable) go12PCToLine(pc uint64) (line int) {
return t.go12PCToVal(pc, t.ptrsize+5*4)
}
// go12PCToSPAdj maps program counter to Stack Pointer adjustment for the Go 1.2 pcln table.
func (t *LineTable) go12PCToSPAdj(pc uint64) (spadj int) {
return t.go12PCToVal(pc, t.ptrsize+3*4)
}
func (t *LineTable) go12PCToVal(pc uint64, fOffset uint32) (val int) {
defer func() {
if recover() != nil {
val = -1
}
}()
f := t.findFunc(pc)
if f == nil {
return -1
}
entry := t.uintptr(f)
linetab := t.binary.Uint32(f[fOffset:])
return int(t.pcvalue(linetab, entry, pc))
}
// go12PCToFile maps program counter to file name for the Go 1.2 pcln table.
func (t *LineTable) go12PCToFile(pc uint64) (file string) {
defer func() {
if recover() != nil {
file = ""
}
}()
f := t.findFunc(pc)
if f == nil {
return ""
}
entry := t.uintptr(f)
filetab := t.binary.Uint32(f[t.ptrsize+4*4:])
fno := t.pcvalue(filetab, entry, pc)
if fno <= 0 {
return ""
}
return t.string(t.binary.Uint32(t.filetab[4*fno:]))
}
// go12LineToPC maps a (file, line) pair to a program counter for the Go 1.2 pcln table.
func (t *LineTable) go12LineToPC(file string, line int) (pc uint64) {
defer func() {
if recover() != nil {
pc = 0
}
}()
t.initFileMap()
filenum := t.fileMap[file]
if filenum == 0 {
return 0
}
// Scan all functions.
// If this turns out to be a bottleneck, we could build a map[int32][]int32
// mapping file number to a list of functions with code from that file.
for i := uint32(0); i < t.nfunctab; i++ {
f := t.Data[t.uintptr(t.functab[2*t.ptrsize*i+t.ptrsize:]):]
entry := t.uintptr(f)
filetab := t.binary.Uint32(f[t.ptrsize+4*4:])
linetab := t.binary.Uint32(f[t.ptrsize+5*4:])
pc := t.findFileLine(entry, filetab, linetab, int32(filenum), int32(line))
if pc != 0 {
return pc
}
}
return 0
}
// initFileMap initializes the map from file name to file number.
func (t *LineTable) initFileMap() {
t.mu.Lock()
defer t.mu.Unlock()
if t.fileMap != nil {
return
}
m := make(map[string]uint32)
for i := uint32(1); i < t.nfiletab; i++ {
s := t.string(t.binary.Uint32(t.filetab[4*i:]))
m[s] = i
}
t.fileMap = m
}
// go12MapFiles adds to m a key for every file in the Go 1.2 LineTable.
// Every key maps to obj. That's not a very interesting map, but it provides
// a way for callers to obtain the list of files in the program.
func (t *LineTable) go12MapFiles(m map[string]*Obj, obj *Obj) {
defer func() {
recover()
}()
t.initFileMap()
for file := range t.fileMap {
m[file] = obj
}
}

View File

@ -0,0 +1,731 @@
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package gosym implements access to the Go symbol
// and line number tables embedded in Go binaries generated
// by the gc compilers.
package gosym
// The table format is a variant of the format used in Plan 9's a.out
// format, documented at http://plan9.bell-labs.com/magic/man2html/6/a.out.
// The best reference for the differences between the Plan 9 format
// and the Go format is the runtime source, specifically ../../runtime/symtab.c.
import (
"bytes"
"encoding/binary"
"fmt"
"strconv"
"strings"
)
/*
* Symbols
*/
// A Sym represents a single symbol table entry.
type Sym struct {
Value uint64
Type byte
Name string
GoType uint64
// If this symbol if a function symbol, the corresponding Func
Func *Func
}
// Static reports whether this symbol is static (not visible outside its file).
func (s *Sym) Static() bool { return s.Type >= 'a' }
// PackageName returns the package part of the symbol name,
// or the empty string if there is none.
func (s *Sym) PackageName() string {
if i := strings.Index(s.Name, "."); i != -1 {
return s.Name[0:i]
}
return ""
}
// ReceiverName returns the receiver type name of this symbol,
// or the empty string if there is none.
func (s *Sym) ReceiverName() string {
l := strings.Index(s.Name, ".")
r := strings.LastIndex(s.Name, ".")
if l == -1 || r == -1 || l == r {
return ""
}
return s.Name[l+1 : r]
}
// BaseName returns the symbol name without the package or receiver name.
func (s *Sym) BaseName() string {
if i := strings.LastIndex(s.Name, "."); i != -1 {
return s.Name[i+1:]
}
return s.Name
}
// A Func collects information about a single function.
type Func struct {
Entry uint64
*Sym
End uint64
Params []*Sym
Locals []*Sym
FrameSize int
LineTable *LineTable
Obj *Obj
}
// An Obj represents a collection of functions in a symbol table.
//
// The exact method of division of a binary into separate Objs is an internal detail
// of the symbol table format.
//
// In early versions of Go each source file became a different Obj.
//
// In Go 1 and Go 1.1, each package produced one Obj for all Go sources
// and one Obj per C source file.
//
// In Go 1.2, there is a single Obj for the entire program.
type Obj struct {
// Funcs is a list of functions in the Obj.
Funcs []Func
// In Go 1.1 and earlier, Paths is a list of symbols corresponding
// to the source file names that produced the Obj.
// In Go 1.2, Paths is nil.
// Use the keys of Table.Files to obtain a list of source files.
Paths []Sym // meta
}
/*
* Symbol tables
*/
// Table represents a Go symbol table. It stores all of the
// symbols decoded from the program and provides methods to translate
// between symbols, names, and addresses.
type Table struct {
Syms []Sym
Funcs []Func
Files map[string]*Obj // nil for Go 1.2 and later binaries
Objs []Obj // nil for Go 1.2 and later binaries
go12line *LineTable // Go 1.2 line number table
}
type sym struct {
value uint64
gotype uint64
typ byte
name []byte
}
var (
littleEndianSymtab = []byte{0xFD, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00}
bigEndianSymtab = []byte{0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00}
oldLittleEndianSymtab = []byte{0xFE, 0xFF, 0xFF, 0xFF, 0x00, 0x00}
)
func walksymtab(data []byte, fn func(sym) error) error {
if len(data) == 0 { // missing symtab is okay
return nil
}
var order binary.ByteOrder = binary.BigEndian
newTable := false
switch {
case bytes.HasPrefix(data, oldLittleEndianSymtab):
// Same as Go 1.0, but little endian.
// Format was used during interim development between Go 1.0 and Go 1.1.
// Should not be widespread, but easy to support.
data = data[6:]
order = binary.LittleEndian
case bytes.HasPrefix(data, bigEndianSymtab):
newTable = true
case bytes.HasPrefix(data, littleEndianSymtab):
newTable = true
order = binary.LittleEndian
}
var ptrsz int
if newTable {
if len(data) < 8 {
return &DecodingError{len(data), "unexpected EOF", nil}
}
ptrsz = int(data[7])
if ptrsz != 4 && ptrsz != 8 {
return &DecodingError{7, "invalid pointer size", ptrsz}
}
data = data[8:]
}
var s sym
p := data
for len(p) >= 4 {
var typ byte
if newTable {
// Symbol type, value, Go type.
typ = p[0] & 0x3F
wideValue := p[0]&0x40 != 0
goType := p[0]&0x80 != 0
if typ < 26 {
typ += 'A'
} else {
typ += 'a' - 26
}
s.typ = typ
p = p[1:]
if wideValue {
if len(p) < ptrsz {
return &DecodingError{len(data), "unexpected EOF", nil}
}
// fixed-width value
if ptrsz == 8 {
s.value = order.Uint64(p[0:8])
p = p[8:]
} else {
s.value = uint64(order.Uint32(p[0:4]))
p = p[4:]
}
} else {
// varint value
s.value = 0
shift := uint(0)
for len(p) > 0 && p[0]&0x80 != 0 {
s.value |= uint64(p[0]&0x7F) << shift
shift += 7
p = p[1:]
}
if len(p) == 0 {
return &DecodingError{len(data), "unexpected EOF", nil}
}
s.value |= uint64(p[0]) << shift
p = p[1:]
}
if goType {
if len(p) < ptrsz {
return &DecodingError{len(data), "unexpected EOF", nil}
}
// fixed-width go type
if ptrsz == 8 {
s.gotype = order.Uint64(p[0:8])
p = p[8:]
} else {
s.gotype = uint64(order.Uint32(p[0:4]))
p = p[4:]
}
}
} else {
// Value, symbol type.
s.value = uint64(order.Uint32(p[0:4]))
if len(p) < 5 {
return &DecodingError{len(data), "unexpected EOF", nil}
}
typ = p[4]
if typ&0x80 == 0 {
return &DecodingError{len(data) - len(p) + 4, "bad symbol type", typ}
}
typ &^= 0x80
s.typ = typ
p = p[5:]
}
// Name.
var i int
var nnul int
for i = 0; i < len(p); i++ {
if p[i] == 0 {
nnul = 1
break
}
}
switch typ {
case 'z', 'Z':
p = p[i+nnul:]
for i = 0; i+2 <= len(p); i += 2 {
if p[i] == 0 && p[i+1] == 0 {
nnul = 2
break
}
}
}
if len(p) < i+nnul {
return &DecodingError{len(data), "unexpected EOF", nil}
}
s.name = p[0:i]
i += nnul
p = p[i:]
if !newTable {
if len(p) < 4 {
return &DecodingError{len(data), "unexpected EOF", nil}
}
// Go type.
s.gotype = uint64(order.Uint32(p[:4]))
p = p[4:]
}
fn(s)
}
return nil
}
// NewTable decodes the Go symbol table in data,
// returning an in-memory representation.
func NewTable(symtab []byte, pcln *LineTable) (*Table, error) {
var n int
err := walksymtab(symtab, func(s sym) error {
n++
return nil
})
if err != nil {
return nil, err
}
var t Table
if pcln.isGo12() {
t.go12line = pcln
}
fname := make(map[uint16]string)
t.Syms = make([]Sym, 0, n)
nf := 0
nz := 0
lasttyp := uint8(0)
err = walksymtab(symtab, func(s sym) error {
n := len(t.Syms)
t.Syms = t.Syms[0 : n+1]
ts := &t.Syms[n]
ts.Type = s.typ
ts.Value = uint64(s.value)
ts.GoType = uint64(s.gotype)
switch s.typ {
default:
// rewrite name to use . instead of · (c2 b7)
w := 0
b := s.name
for i := 0; i < len(b); i++ {
if b[i] == 0xc2 && i+1 < len(b) && b[i+1] == 0xb7 {
i++
b[i] = '.'
}
b[w] = b[i]
w++
}
ts.Name = string(s.name[0:w])
case 'z', 'Z':
if lasttyp != 'z' && lasttyp != 'Z' {
nz++
}
for i := 0; i < len(s.name); i += 2 {
eltIdx := binary.BigEndian.Uint16(s.name[i : i+2])
elt, ok := fname[eltIdx]
if !ok {
return &DecodingError{-1, "bad filename code", eltIdx}
}
if n := len(ts.Name); n > 0 && ts.Name[n-1] != '/' {
ts.Name += "/"
}
ts.Name += elt
}
}
switch s.typ {
case 'T', 't', 'L', 'l':
nf++
case 'f':
fname[uint16(s.value)] = ts.Name
}
lasttyp = s.typ
return nil
})
if err != nil {
return nil, err
}
t.Funcs = make([]Func, 0, nf)
t.Files = make(map[string]*Obj)
var obj *Obj
if t.go12line != nil {
// Put all functions into one Obj.
t.Objs = make([]Obj, 1)
obj = &t.Objs[0]
t.go12line.go12MapFiles(t.Files, obj)
} else {
t.Objs = make([]Obj, 0, nz)
}
// Count text symbols and attach frame sizes, parameters, and
// locals to them. Also, find object file boundaries.
lastf := 0
for i := 0; i < len(t.Syms); i++ {
sym := &t.Syms[i]
switch sym.Type {
case 'Z', 'z': // path symbol
if t.go12line != nil {
// Go 1.2 binaries have the file information elsewhere. Ignore.
break
}
// Finish the current object
if obj != nil {
obj.Funcs = t.Funcs[lastf:]
}
lastf = len(t.Funcs)
// Start new object
n := len(t.Objs)
t.Objs = t.Objs[0 : n+1]
obj = &t.Objs[n]
// Count & copy path symbols
var end int
for end = i + 1; end < len(t.Syms); end++ {
if c := t.Syms[end].Type; c != 'Z' && c != 'z' {
break
}
}
obj.Paths = t.Syms[i:end]
i = end - 1 // loop will i++
// Record file names
depth := 0
for j := range obj.Paths {
s := &obj.Paths[j]
if s.Name == "" {
depth--
} else {
if depth == 0 {
t.Files[s.Name] = obj
}
depth++
}
}
case 'T', 't', 'L', 'l': // text symbol
if n := len(t.Funcs); n > 0 {
t.Funcs[n-1].End = sym.Value
}
if sym.Name == "etext" {
continue
}
// Count parameter and local (auto) syms
var np, na int
var end int
countloop:
for end = i + 1; end < len(t.Syms); end++ {
switch t.Syms[end].Type {
case 'T', 't', 'L', 'l', 'Z', 'z':
break countloop
case 'p':
np++
case 'a':
na++
}
}
// Fill in the function symbol
n := len(t.Funcs)
t.Funcs = t.Funcs[0 : n+1]
fn := &t.Funcs[n]
sym.Func = fn
fn.Params = make([]*Sym, 0, np)
fn.Locals = make([]*Sym, 0, na)
fn.Sym = sym
fn.Entry = sym.Value
fn.Obj = obj
if t.go12line != nil {
// All functions share the same line table.
// It knows how to narrow down to a specific
// function quickly.
fn.LineTable = t.go12line
} else if pcln != nil {
fn.LineTable = pcln.slice(fn.Entry)
pcln = fn.LineTable
}
for j := i; j < end; j++ {
s := &t.Syms[j]
switch s.Type {
case 'm':
fn.FrameSize = int(s.Value)
case 'p':
n := len(fn.Params)
fn.Params = fn.Params[0 : n+1]
fn.Params[n] = s
case 'a':
n := len(fn.Locals)
fn.Locals = fn.Locals[0 : n+1]
fn.Locals[n] = s
}
}
i = end - 1 // loop will i++
}
}
if t.go12line != nil && nf == 0 {
t.Funcs = t.go12line.go12Funcs()
}
if obj != nil {
obj.Funcs = t.Funcs[lastf:]
}
return &t, nil
}
// PCToFunc returns the function containing the program counter pc,
// or nil if there is no such function.
func (t *Table) PCToFunc(pc uint64) *Func {
funcs := t.Funcs
for len(funcs) > 0 {
m := len(funcs) / 2
fn := &funcs[m]
switch {
case pc < fn.Entry:
funcs = funcs[0:m]
case fn.Entry <= pc && pc < fn.End:
return fn
default:
funcs = funcs[m+1:]
}
}
return nil
}
// PCToLine looks up line number information for a program counter.
// If there is no information, it returns fn == nil.
func (t *Table) PCToLine(pc uint64) (file string, line int, fn *Func) {
if fn = t.PCToFunc(pc); fn == nil {
return
}
if t.go12line != nil {
file = t.go12line.go12PCToFile(pc)
line = t.go12line.go12PCToLine(pc)
} else {
file, line = fn.Obj.lineFromAline(fn.LineTable.PCToLine(pc))
}
return
}
// PCToSPAdj returns the stack pointer adjustment for a program counter.
func (t *Table) PCToSPAdj(pc uint64) (spadj int) {
if fn := t.PCToFunc(pc); fn == nil {
return 0
}
if t.go12line != nil {
return t.go12line.go12PCToSPAdj(pc)
}
return 0
}
// LineToPC looks up the first program counter on the given line in
// the named file. It returns UnknownPathError or UnknownLineError if
// there is an error looking up this line.
func (t *Table) LineToPC(file string, line int) (pc uint64, fn *Func, err error) {
obj, ok := t.Files[file]
if !ok {
return 0, nil, UnknownFileError(file)
}
if t.go12line != nil {
pc := t.go12line.go12LineToPC(file, line)
if pc == 0 {
return 0, nil, &UnknownLineError{file, line}
}
return pc, t.PCToFunc(pc), nil
}
abs, err := obj.alineFromLine(file, line)
if err != nil {
return
}
for i := range obj.Funcs {
f := &obj.Funcs[i]
pc := f.LineTable.LineToPC(abs, f.End)
if pc != 0 {
return pc, f, nil
}
}
return 0, nil, &UnknownLineError{file, line}
}
// LookupSym returns the text, data, or bss symbol with the given name,
// or nil if no such symbol is found.
func (t *Table) LookupSym(name string) *Sym {
// TODO(austin) Maybe make a map
for i := range t.Syms {
s := &t.Syms[i]
switch s.Type {
case 'T', 't', 'L', 'l', 'D', 'd', 'B', 'b':
if s.Name == name {
return s
}
}
}
return nil
}
// LookupFunc returns the text, data, or bss symbol with the given name,
// or nil if no such symbol is found.
func (t *Table) LookupFunc(name string) *Func {
for i := range t.Funcs {
f := &t.Funcs[i]
if f.Sym.Name == name {
return f
}
}
return nil
}
// SymByAddr returns the text, data, or bss symbol starting at the given address.
func (t *Table) SymByAddr(addr uint64) *Sym {
for i := range t.Syms {
s := &t.Syms[i]
switch s.Type {
case 'T', 't', 'L', 'l', 'D', 'd', 'B', 'b':
if s.Value == addr {
return s
}
}
}
return nil
}
/*
* Object files
*/
// This is legacy code for Go 1.1 and earlier, which used the
// Plan 9 format for pc-line tables. This code was never quite
// correct. It's probably very close, and it's usually correct, but
// we never quite found all the corner cases.
//
// Go 1.2 and later use a simpler format, documented at golang.org/s/go12symtab.
func (o *Obj) lineFromAline(aline int) (string, int) {
type stackEnt struct {
path string
start int
offset int
prev *stackEnt
}
noPath := &stackEnt{"", 0, 0, nil}
tos := noPath
pathloop:
for _, s := range o.Paths {
val := int(s.Value)
switch {
case val > aline:
break pathloop
case val == 1:
// Start a new stack
tos = &stackEnt{s.Name, val, 0, noPath}
case s.Name == "":
// Pop
if tos == noPath {
return "<malformed symbol table>", 0
}
tos.prev.offset += val - tos.start
tos = tos.prev
default:
// Push
tos = &stackEnt{s.Name, val, 0, tos}
}
}
if tos == noPath {
return "", 0
}
return tos.path, aline - tos.start - tos.offset + 1
}
func (o *Obj) alineFromLine(path string, line int) (int, error) {
if line < 1 {
return 0, &UnknownLineError{path, line}
}
for i, s := range o.Paths {
// Find this path
if s.Name != path {
continue
}
// Find this line at this stack level
depth := 0
var incstart int
line += int(s.Value)
pathloop:
for _, s := range o.Paths[i:] {
val := int(s.Value)
switch {
case depth == 1 && val >= line:
return line - 1, nil
case s.Name == "":
depth--
if depth == 0 {
break pathloop
} else if depth == 1 {
line += val - incstart
}
default:
if depth == 1 {
incstart = val
}
depth++
}
}
return 0, &UnknownLineError{path, line}
}
return 0, UnknownFileError(path)
}
/*
* Errors
*/
// UnknownFileError represents a failure to find the specific file in
// the symbol table.
type UnknownFileError string
func (e UnknownFileError) Error() string { return "unknown file: " + string(e) }
// UnknownLineError represents a failure to map a line to a program
// counter, either because the line is beyond the bounds of the file
// or because there is no code on the given line.
type UnknownLineError struct {
File string
Line int
}
func (e *UnknownLineError) Error() string {
return "no code at " + e.File + ":" + strconv.Itoa(e.Line)
}
// DecodingError represents an error during the decoding of
// the symbol table.
type DecodingError struct {
off int
msg string
val interface{}
}
func (e *DecodingError) Error() string {
msg := e.msg
if e.val != nil {
msg += fmt.Sprintf(" '%v'", e.val)
}
msg += fmt.Sprintf(" at byte %#x", e.off)
return msg
}

View File

@ -1,4 +1,4 @@
// Copyright 2014 Google Inc. All Rights Reserved.
// Copyright 2014 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -20,6 +20,7 @@
package metadata // import "cloud.google.com/go/compute/metadata"
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
@ -31,11 +32,6 @@ import (
"strings"
"sync"
"time"
"golang.org/x/net/context"
"golang.org/x/net/context/ctxhttp"
"cloud.google.com/go/internal"
)
const (
@ -48,6 +44,8 @@ const (
// This is variable name is not defined by any spec, as far as
// I know; it was made up for the Go package.
metadataHostEnv = "GCE_METADATA_HOST"
userAgent = "gcloud-golang/0.1"
)
type cachedValue struct {
@ -64,27 +62,22 @@ var (
)
var (
metaClient = &http.Client{
Transport: &internal.Transport{
Base: &http.Transport{
Dial: (&net.Dialer{
Timeout: 2 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
ResponseHeaderTimeout: 2 * time.Second,
},
defaultClient = &Client{hc: &http.Client{
Transport: &http.Transport{
Dial: (&net.Dialer{
Timeout: 2 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
},
}
subscribeClient = &http.Client{
Transport: &internal.Transport{
Base: &http.Transport{
Dial: (&net.Dialer{
Timeout: 2 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
},
}}
subscribeClient = &Client{hc: &http.Client{
Transport: &http.Transport{
Dial: (&net.Dialer{
Timeout: 2 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
},
}
}}
)
// NotDefinedError is returned when requested metadata is not defined.
@ -99,73 +92,16 @@ func (suffix NotDefinedError) Error() string {
return fmt.Sprintf("metadata: GCE metadata %q not defined", string(suffix))
}
// Get returns a value from the metadata service.
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
//
// If the GCE_METADATA_HOST environment variable is not defined, a default of
// 169.254.169.254 will be used instead.
//
// If the requested metadata is not defined, the returned error will
// be of type NotDefinedError.
func Get(suffix string) (string, error) {
val, _, err := getETag(metaClient, suffix)
return val, err
}
// getETag returns a value from the metadata service as well as the associated
// ETag using the provided client. This func is otherwise equivalent to Get.
func getETag(client *http.Client, suffix string) (value, etag string, err error) {
// Using a fixed IP makes it very difficult to spoof the metadata service in
// a container, which is an important use-case for local testing of cloud
// deployments. To enable spoofing of the metadata service, the environment
// variable GCE_METADATA_HOST is first inspected to decide where metadata
// requests shall go.
host := os.Getenv(metadataHostEnv)
if host == "" {
// Using 169.254.169.254 instead of "metadata" here because Go
// binaries built with the "netgo" tag and without cgo won't
// know the search suffix for "metadata" is
// ".google.internal", and this IP address is documented as
// being stable anyway.
host = metadataIP
}
url := "http://" + host + "/computeMetadata/v1/" + suffix
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Metadata-Flavor", "Google")
res, err := client.Do(req)
if err != nil {
return "", "", err
}
defer res.Body.Close()
if res.StatusCode == http.StatusNotFound {
return "", "", NotDefinedError(suffix)
}
if res.StatusCode != 200 {
return "", "", fmt.Errorf("status code %d trying to fetch %s", res.StatusCode, url)
}
all, err := ioutil.ReadAll(res.Body)
if err != nil {
return "", "", err
}
return string(all), res.Header.Get("Etag"), nil
}
func getTrimmed(suffix string) (s string, err error) {
s, err = Get(suffix)
s = strings.TrimSpace(s)
return
}
func (c *cachedValue) get() (v string, err error) {
func (c *cachedValue) get(cl *Client) (v string, err error) {
defer c.mu.Unlock()
c.mu.Lock()
if c.v != "" {
return c.v, nil
}
if c.trim {
v, err = getTrimmed(c.k)
v, err = cl.getTrimmed(c.k)
} else {
v, err = Get(c.k)
v, err = cl.Get(c.k)
}
if err == nil {
c.v = v
@ -200,9 +136,11 @@ func testOnGCE() bool {
resc := make(chan bool, 2)
// Try two strategies in parallel.
// See https://github.com/GoogleCloudPlatform/google-cloud-go/issues/194
// See https://github.com/googleapis/google-cloud-go/issues/194
go func() {
res, err := ctxhttp.Get(ctx, metaClient, "http://"+metadataIP)
req, _ := http.NewRequest("GET", "http://"+metadataIP, nil)
req.Header.Set("User-Agent", userAgent)
res, err := defaultClient.hc.Do(req.WithContext(ctx))
if err != nil {
resc <- false
return
@ -267,6 +205,267 @@ func systemInfoSuggestsGCE() bool {
return name == "Google" || name == "Google Compute Engine"
}
// Subscribe calls Client.Subscribe on a client designed for subscribing (one with no
// ResponseHeaderTimeout).
func Subscribe(suffix string, fn func(v string, ok bool) error) error {
return subscribeClient.Subscribe(suffix, fn)
}
// Get calls Client.Get on the default client.
func Get(suffix string) (string, error) { return defaultClient.Get(suffix) }
// ProjectID returns the current instance's project ID string.
func ProjectID() (string, error) { return defaultClient.ProjectID() }
// NumericProjectID returns the current instance's numeric project ID.
func NumericProjectID() (string, error) { return defaultClient.NumericProjectID() }
// InternalIP returns the instance's primary internal IP address.
func InternalIP() (string, error) { return defaultClient.InternalIP() }
// ExternalIP returns the instance's primary external (public) IP address.
func ExternalIP() (string, error) { return defaultClient.ExternalIP() }
// Email calls Client.Email on the default client.
func Email(serviceAccount string) (string, error) { return defaultClient.Email(serviceAccount) }
// Hostname returns the instance's hostname. This will be of the form
// "<instanceID>.c.<projID>.internal".
func Hostname() (string, error) { return defaultClient.Hostname() }
// InstanceTags returns the list of user-defined instance tags,
// assigned when initially creating a GCE instance.
func InstanceTags() ([]string, error) { return defaultClient.InstanceTags() }
// InstanceID returns the current VM's numeric instance ID.
func InstanceID() (string, error) { return defaultClient.InstanceID() }
// InstanceName returns the current VM's instance ID string.
func InstanceName() (string, error) { return defaultClient.InstanceName() }
// Zone returns the current VM's zone, such as "us-central1-b".
func Zone() (string, error) { return defaultClient.Zone() }
// InstanceAttributes calls Client.InstanceAttributes on the default client.
func InstanceAttributes() ([]string, error) { return defaultClient.InstanceAttributes() }
// ProjectAttributes calls Client.ProjectAttributes on the default client.
func ProjectAttributes() ([]string, error) { return defaultClient.ProjectAttributes() }
// InstanceAttributeValue calls Client.InstanceAttributeValue on the default client.
func InstanceAttributeValue(attr string) (string, error) {
return defaultClient.InstanceAttributeValue(attr)
}
// ProjectAttributeValue calls Client.ProjectAttributeValue on the default client.
func ProjectAttributeValue(attr string) (string, error) {
return defaultClient.ProjectAttributeValue(attr)
}
// Scopes calls Client.Scopes on the default client.
func Scopes(serviceAccount string) ([]string, error) { return defaultClient.Scopes(serviceAccount) }
func strsContains(ss []string, s string) bool {
for _, v := range ss {
if v == s {
return true
}
}
return false
}
// A Client provides metadata.
type Client struct {
hc *http.Client
}
// NewClient returns a Client that can be used to fetch metadata. All HTTP requests
// will use the given http.Client instead of the default client.
func NewClient(c *http.Client) *Client {
return &Client{hc: c}
}
// getETag returns a value from the metadata service as well as the associated ETag.
// This func is otherwise equivalent to Get.
func (c *Client) getETag(suffix string) (value, etag string, err error) {
// Using a fixed IP makes it very difficult to spoof the metadata service in
// a container, which is an important use-case for local testing of cloud
// deployments. To enable spoofing of the metadata service, the environment
// variable GCE_METADATA_HOST is first inspected to decide where metadata
// requests shall go.
host := os.Getenv(metadataHostEnv)
if host == "" {
// Using 169.254.169.254 instead of "metadata" here because Go
// binaries built with the "netgo" tag and without cgo won't
// know the search suffix for "metadata" is
// ".google.internal", and this IP address is documented as
// being stable anyway.
host = metadataIP
}
u := "http://" + host + "/computeMetadata/v1/" + suffix
req, err := http.NewRequest("GET", u, nil)
if err != nil {
return "", "", err
}
req.Header.Set("Metadata-Flavor", "Google")
req.Header.Set("User-Agent", userAgent)
res, err := c.hc.Do(req)
if err != nil {
return "", "", err
}
defer res.Body.Close()
if res.StatusCode == http.StatusNotFound {
return "", "", NotDefinedError(suffix)
}
all, err := ioutil.ReadAll(res.Body)
if err != nil {
return "", "", err
}
if res.StatusCode != 200 {
return "", "", &Error{Code: res.StatusCode, Message: string(all)}
}
return string(all), res.Header.Get("Etag"), nil
}
// Get returns a value from the metadata service.
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
//
// If the GCE_METADATA_HOST environment variable is not defined, a default of
// 169.254.169.254 will be used instead.
//
// If the requested metadata is not defined, the returned error will
// be of type NotDefinedError.
func (c *Client) Get(suffix string) (string, error) {
val, _, err := c.getETag(suffix)
return val, err
}
func (c *Client) getTrimmed(suffix string) (s string, err error) {
s, err = c.Get(suffix)
s = strings.TrimSpace(s)
return
}
func (c *Client) lines(suffix string) ([]string, error) {
j, err := c.Get(suffix)
if err != nil {
return nil, err
}
s := strings.Split(strings.TrimSpace(j), "\n")
for i := range s {
s[i] = strings.TrimSpace(s[i])
}
return s, nil
}
// ProjectID returns the current instance's project ID string.
func (c *Client) ProjectID() (string, error) { return projID.get(c) }
// NumericProjectID returns the current instance's numeric project ID.
func (c *Client) NumericProjectID() (string, error) { return projNum.get(c) }
// InstanceID returns the current VM's numeric instance ID.
func (c *Client) InstanceID() (string, error) { return instID.get(c) }
// InternalIP returns the instance's primary internal IP address.
func (c *Client) InternalIP() (string, error) {
return c.getTrimmed("instance/network-interfaces/0/ip")
}
// Email returns the email address associated with the service account.
// The account may be empty or the string "default" to use the instance's
// main account.
func (c *Client) Email(serviceAccount string) (string, error) {
if serviceAccount == "" {
serviceAccount = "default"
}
return c.getTrimmed("instance/service-accounts/" + serviceAccount + "/email")
}
// ExternalIP returns the instance's primary external (public) IP address.
func (c *Client) ExternalIP() (string, error) {
return c.getTrimmed("instance/network-interfaces/0/access-configs/0/external-ip")
}
// Hostname returns the instance's hostname. This will be of the form
// "<instanceID>.c.<projID>.internal".
func (c *Client) Hostname() (string, error) {
return c.getTrimmed("instance/hostname")
}
// InstanceTags returns the list of user-defined instance tags,
// assigned when initially creating a GCE instance.
func (c *Client) InstanceTags() ([]string, error) {
var s []string
j, err := c.Get("instance/tags")
if err != nil {
return nil, err
}
if err := json.NewDecoder(strings.NewReader(j)).Decode(&s); err != nil {
return nil, err
}
return s, nil
}
// InstanceName returns the current VM's instance ID string.
func (c *Client) InstanceName() (string, error) {
return c.getTrimmed("instance/name")
}
// Zone returns the current VM's zone, such as "us-central1-b".
func (c *Client) Zone() (string, error) {
zone, err := c.getTrimmed("instance/zone")
// zone is of the form "projects/<projNum>/zones/<zoneName>".
if err != nil {
return "", err
}
return zone[strings.LastIndex(zone, "/")+1:], nil
}
// InstanceAttributes returns the list of user-defined attributes,
// assigned when initially creating a GCE VM instance. The value of an
// attribute can be obtained with InstanceAttributeValue.
func (c *Client) InstanceAttributes() ([]string, error) { return c.lines("instance/attributes/") }
// ProjectAttributes returns the list of user-defined attributes
// applying to the project as a whole, not just this VM. The value of
// an attribute can be obtained with ProjectAttributeValue.
func (c *Client) ProjectAttributes() ([]string, error) { return c.lines("project/attributes/") }
// InstanceAttributeValue returns the value of the provided VM
// instance attribute.
//
// If the requested attribute is not defined, the returned error will
// be of type NotDefinedError.
//
// InstanceAttributeValue may return ("", nil) if the attribute was
// defined to be the empty string.
func (c *Client) InstanceAttributeValue(attr string) (string, error) {
return c.Get("instance/attributes/" + attr)
}
// ProjectAttributeValue returns the value of the provided
// project attribute.
//
// If the requested attribute is not defined, the returned error will
// be of type NotDefinedError.
//
// ProjectAttributeValue may return ("", nil) if the attribute was
// defined to be the empty string.
func (c *Client) ProjectAttributeValue(attr string) (string, error) {
return c.Get("project/attributes/" + attr)
}
// Scopes returns the service account scopes for the given account.
// The account may be empty or the string "default" to use the instance's
// main account.
func (c *Client) Scopes(serviceAccount string) ([]string, error) {
if serviceAccount == "" {
serviceAccount = "default"
}
return c.lines("instance/service-accounts/" + serviceAccount + "/scopes")
}
// Subscribe subscribes to a value from the metadata service.
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
// The suffix may contain query parameters.
@ -276,11 +475,11 @@ func systemInfoSuggestsGCE() bool {
// and ok false. Subscribe blocks until fn returns a non-nil error or the value
// is deleted. Subscribe returns the error value returned from the last call to
// fn, which may be nil when ok == false.
func Subscribe(suffix string, fn func(v string, ok bool) error) error {
func (c *Client) Subscribe(suffix string, fn func(v string, ok bool) error) error {
const failedSubscribeSleep = time.Second * 5
// First check to see if the metadata value exists at all.
val, lastETag, err := getETag(subscribeClient, suffix)
val, lastETag, err := c.getETag(suffix)
if err != nil {
return err
}
@ -296,7 +495,7 @@ func Subscribe(suffix string, fn func(v string, ok bool) error) error {
suffix += "?wait_for_change=true&last_etag="
}
for {
val, etag, err := getETag(subscribeClient, suffix+url.QueryEscape(lastETag))
val, etag, err := c.getETag(suffix + url.QueryEscape(lastETag))
if err != nil {
if _, deleted := err.(NotDefinedError); !deleted {
time.Sleep(failedSubscribeSleep)
@ -312,127 +511,14 @@ func Subscribe(suffix string, fn func(v string, ok bool) error) error {
}
}
// ProjectID returns the current instance's project ID string.
func ProjectID() (string, error) { return projID.get() }
// NumericProjectID returns the current instance's numeric project ID.
func NumericProjectID() (string, error) { return projNum.get() }
// InternalIP returns the instance's primary internal IP address.
func InternalIP() (string, error) {
return getTrimmed("instance/network-interfaces/0/ip")
// Error contains an error response from the server.
type Error struct {
// Code is the HTTP response status code.
Code int
// Message is the server response message.
Message string
}
// ExternalIP returns the instance's primary external (public) IP address.
func ExternalIP() (string, error) {
return getTrimmed("instance/network-interfaces/0/access-configs/0/external-ip")
}
// Hostname returns the instance's hostname. This will be of the form
// "<instanceID>.c.<projID>.internal".
func Hostname() (string, error) {
return getTrimmed("instance/hostname")
}
// InstanceTags returns the list of user-defined instance tags,
// assigned when initially creating a GCE instance.
func InstanceTags() ([]string, error) {
var s []string
j, err := Get("instance/tags")
if err != nil {
return nil, err
}
if err := json.NewDecoder(strings.NewReader(j)).Decode(&s); err != nil {
return nil, err
}
return s, nil
}
// InstanceID returns the current VM's numeric instance ID.
func InstanceID() (string, error) {
return instID.get()
}
// InstanceName returns the current VM's instance ID string.
func InstanceName() (string, error) {
host, err := Hostname()
if err != nil {
return "", err
}
return strings.Split(host, ".")[0], nil
}
// Zone returns the current VM's zone, such as "us-central1-b".
func Zone() (string, error) {
zone, err := getTrimmed("instance/zone")
// zone is of the form "projects/<projNum>/zones/<zoneName>".
if err != nil {
return "", err
}
return zone[strings.LastIndex(zone, "/")+1:], nil
}
// InstanceAttributes returns the list of user-defined attributes,
// assigned when initially creating a GCE VM instance. The value of an
// attribute can be obtained with InstanceAttributeValue.
func InstanceAttributes() ([]string, error) { return lines("instance/attributes/") }
// ProjectAttributes returns the list of user-defined attributes
// applying to the project as a whole, not just this VM. The value of
// an attribute can be obtained with ProjectAttributeValue.
func ProjectAttributes() ([]string, error) { return lines("project/attributes/") }
func lines(suffix string) ([]string, error) {
j, err := Get(suffix)
if err != nil {
return nil, err
}
s := strings.Split(strings.TrimSpace(j), "\n")
for i := range s {
s[i] = strings.TrimSpace(s[i])
}
return s, nil
}
// InstanceAttributeValue returns the value of the provided VM
// instance attribute.
//
// If the requested attribute is not defined, the returned error will
// be of type NotDefinedError.
//
// InstanceAttributeValue may return ("", nil) if the attribute was
// defined to be the empty string.
func InstanceAttributeValue(attr string) (string, error) {
return Get("instance/attributes/" + attr)
}
// ProjectAttributeValue returns the value of the provided
// project attribute.
//
// If the requested attribute is not defined, the returned error will
// be of type NotDefinedError.
//
// ProjectAttributeValue may return ("", nil) if the attribute was
// defined to be the empty string.
func ProjectAttributeValue(attr string) (string, error) {
return Get("project/attributes/" + attr)
}
// Scopes returns the service account scopes for the given account.
// The account may be empty or the string "default" to use the instance's
// main account.
func Scopes(serviceAccount string) ([]string, error) {
if serviceAccount == "" {
serviceAccount = "default"
}
return lines("instance/service-accounts/" + serviceAccount + "/scopes")
}
func strsContains(ss []string, s string) bool {
for _, v := range ss {
if v == s {
return true
}
}
return false
func (e *Error) Error() string {
return fmt.Sprintf("compute: Received %d `%s`", e.Code, e.Message)
}

31
src/cmd/linuxkit/vendor/cloud.google.com/go/go.mod generated vendored Normal file
View File

@ -0,0 +1,31 @@
module cloud.google.com/go
go 1.11
require (
cloud.google.com/go/bigquery v1.3.0
cloud.google.com/go/datastore v1.0.0
cloud.google.com/go/pubsub v1.1.0
cloud.google.com/go/storage v1.5.0
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
github.com/golang/mock v1.4.0
github.com/golang/protobuf v1.3.3
github.com/google/go-cmp v0.4.0
github.com/google/martian v2.1.0+incompatible
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12
github.com/googleapis/gax-go/v2 v2.0.5
github.com/jstemmer/go-junit-report v0.9.1
go.opencensus.io v0.22.3
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd
golang.org/x/lint v0.0.0-20200130185559-910be7a94367
golang.org/x/mod v0.2.0 // indirect
golang.org/x/net v0.0.0-20200202094626-16171245cfb2
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4 // indirect
golang.org/x/text v0.3.2
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56
google.golang.org/api v0.17.0
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce
google.golang.org/grpc v1.27.1
honnef.co/go/tools v0.0.1-2019.2.3
)

View File

@ -1,64 +0,0 @@
// Copyright 2014 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package internal provides support for the cloud packages.
//
// Users should not import this package directly.
package internal
import (
"fmt"
"net/http"
)
const userAgent = "gcloud-golang/0.1"
// Transport is an http.RoundTripper that appends Google Cloud client's
// user-agent to the original request's user-agent header.
type Transport struct {
// TODO(bradfitz): delete internal.Transport. It's too wrappy for what it does.
// Do User-Agent some other way.
// Base is the actual http.RoundTripper
// requests will use. It must not be nil.
Base http.RoundTripper
}
// RoundTrip appends a user-agent to the existing user-agent
// header and delegates the request to the base http.RoundTripper.
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
req = cloneRequest(req)
ua := req.Header.Get("User-Agent")
if ua == "" {
ua = userAgent
} else {
ua = fmt.Sprintf("%s %s", ua, userAgent)
}
req.Header.Set("User-Agent", ua)
return t.Base.RoundTrip(req)
}
// cloneRequest returns a clone of the provided *http.Request.
// The clone is a shallow copy of the struct and its Header map.
func cloneRequest(r *http.Request) *http.Request {
// shallow copy of the struct
r2 := new(http.Request)
*r2 = *r
// deep copy of the Header
r2.Header = make(http.Header)
for k, s := range r.Header {
r2.Header[k] = s
}
return r2
}

View File

@ -1,55 +0,0 @@
// Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package internal
import (
"fmt"
"time"
gax "github.com/googleapis/gax-go"
"golang.org/x/net/context"
)
// Retry calls the supplied function f repeatedly according to the provided
// backoff parameters. It returns when one of the following occurs:
// When f's first return value is true, Retry immediately returns with f's second
// return value.
// When the provided context is done, Retry returns with ctx.Err().
func Retry(ctx context.Context, bo gax.Backoff, f func() (stop bool, err error)) error {
return retry(ctx, bo, f, gax.Sleep)
}
func retry(ctx context.Context, bo gax.Backoff, f func() (stop bool, err error),
sleep func(context.Context, time.Duration) error) error {
var lastErr error
for {
stop, err := f()
if stop {
return err
}
// Remember the last "real" error from f.
if err != nil && err != context.Canceled && err != context.DeadlineExceeded {
lastErr = err
}
p := bo.Pause()
if cerr := sleep(ctx, p); cerr != nil {
if lastErr != nil {
return fmt.Errorf("%v; last function err: %v", cerr, lastErr)
}
return cerr
}
}
}

View File

@ -1,202 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2015 Docker, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -1,11 +0,0 @@
# p9p [![GoDoc](https://godoc.org/github.com/docker/go-p9p?status.svg)](https://godoc.org/github.com/docker/go-p9p) [![Apache licensed](https://img.shields.io/badge/license-Apache-blue.svg)](https://raw.githubusercontent.com/docker/go-p9p/master/LICENSE) [![CircleCI](https://circleci.com/gh/docker/go-p9p.svg?style=shield)](https://circleci.com/gh/docker/go-p9p) [![TravisCI](https://travis-ci.org/docker/go-p9p.svg?branch=master)](https://travis-ci.org/docker/go-p9p) [![Go Report Card](https://goreportcard.com/badge/github.com/docker/go-p9p)](https://goreportcard.com/report/github.com/docker/go-p9p) [![Badge Badge](http://doyouevenbadge.com/github.com/docker/go-p9p)](http://doyouevenbadge.com/report/github.com/docker/go-p9p)
A modern, performant 9P library for Go.
For information on usage, please see the [GoDoc](https://godoc.org/github.com/docker/go-p9p).
## Copyright and license
Copyright © 2015 Docker, Inc. go-p9p is licensed under the Apache License,
Version 2.0. See [LICENSE](LICENSE) for the full license text.

View File

@ -1,343 +0,0 @@
package p9p
import (
"bufio"
"context"
"encoding/binary"
"io"
"io/ioutil"
"log"
"net"
"time"
)
const (
// channelMessageHeaderSize is the overhead for sending the size of a
// message on the wire.
channelMessageHeaderSize = 4
)
// Channel defines the operations necessary to implement a 9p message channel
// interface. Typically, message channels do no protocol processing except to
// send and receive message frames.
type Channel interface {
// ReadFcall reads one fcall frame into the provided fcall structure. The
// Fcall may be cleared whether there is an error or not. If the operation
// is successful, the contents of the fcall will be populated in the
// argument. ReadFcall cannot be called concurrently with other calls to
// ReadFcall. This both to preserve message ordering and to allow lockless
// buffer reusage.
ReadFcall(ctx context.Context, fcall *Fcall) error
// WriteFcall writes the provided fcall to the channel. WriteFcall cannot
// be called concurrently with other calls to WriteFcall.
WriteFcall(ctx context.Context, fcall *Fcall) error
// MSize returns the current msize for the channel.
MSize() int
// SetMSize sets the maximum message size for the channel. This must never
// be called currently with ReadFcall or WriteFcall.
SetMSize(msize int)
}
// NewChannel returns a new channel to read and write Fcalls with the provided
// connection and message size.
func NewChannel(conn net.Conn, msize int) Channel {
return newChannel(conn, codec9p{}, msize)
}
const (
defaultRWTimeout = 30 * time.Second // default read/write timeout if not set in context
)
// channel provides bidirectional protocol framing for 9p over net.Conn.
// Operations are not thread-safe but reads and writes may be carried out
// concurrently, supporting separate read and write loops.
//
// Lifecyle
//
// A connection, or message channel abstraction, has a lifecycle delineated by
// Tversion/Rversion request response cycles. For now, this is part of the
// channel itself but doesn't necessarily influence the channels state, except
// the msize. Visually, it might look something like this:
//
// [Established] -> [Version] -> [Session] -> [Version]---+
// ^ |
// |_________________________________|
//
// The connection is established, then we negotiate a version, run a session,
// then negotiate a version and so on. For most purposes, we are likely going
// to terminate the connection after the session but we may want to support
// connection pooling. Pooling may result in possible security leaks if the
// connections are shared among contexts, since the version is negotiated at
// the start of the session. To avoid this, we can actually use a "tombstone"
// version message which clears the server's session state without starting a
// new session. The next version message would then prepare the session
// without leaking any Fid's.
type channel struct {
conn net.Conn
codec Codec
brd *bufio.Reader
bwr *bufio.Writer
closed chan struct{}
msize int
rdbuf []byte
}
func newChannel(conn net.Conn, codec Codec, msize int) *channel {
return &channel{
conn: conn,
codec: codec,
brd: bufio.NewReaderSize(conn, msize), // msize may not be optimal buffer size
bwr: bufio.NewWriterSize(conn, msize),
closed: make(chan struct{}),
msize: msize,
rdbuf: make([]byte, msize),
}
}
func (ch *channel) MSize() int {
return ch.msize
}
// setmsize resizes the buffers for use with a separate msize. This call must
// be protected by a mutex or made before passing to other goroutines.
func (ch *channel) SetMSize(msize int) {
// NOTE(stevvooe): We cannot safely resize the buffered reader and writer.
// Proceed assuming that original size is sufficient.
ch.msize = msize
if msize < len(ch.rdbuf) {
// just change the cap
ch.rdbuf = ch.rdbuf[:msize]
return
}
ch.rdbuf = make([]byte, msize)
}
// ReadFcall reads the next message from the channel into fcall.
//
// If the incoming message overflows the msize, Overflow(err) will return
// nonzero with the number of bytes overflowed.
func (ch *channel) ReadFcall(ctx context.Context, fcall *Fcall) error {
select {
case <-ctx.Done():
return ctx.Err()
case <-ch.closed:
return ErrClosed
default:
}
deadline, ok := ctx.Deadline()
if !ok {
deadline = time.Now().Add(defaultRWTimeout)
}
if err := ch.conn.SetReadDeadline(deadline); err != nil {
log.Printf("transport: error setting read deadline on %v: %v", ch.conn.RemoteAddr(), err)
}
n, err := readmsg(ch.brd, ch.rdbuf)
if err != nil {
// TODO(stevvooe): There may be more we can do here to detect partial
// reads. For now, we just propagate the error untouched.
return err
}
if n > len(ch.rdbuf) {
return overflowErr{size: n - len(ch.rdbuf)}
}
// clear out the fcall
*fcall = Fcall{}
if err := ch.codec.Unmarshal(ch.rdbuf[:n], fcall); err != nil {
return err
}
if err := ch.maybeTruncate(fcall); err != nil {
return err
}
return nil
}
// WriteFcall writes the message to the connection.
//
// If a message destined for the wire will overflow MSize, an Overflow error
// may be returned. For Twrite calls, the buffer will simply be truncated to
// the optimal msize, with the caller detecting this condition with
// Rwrite.Count.
func (ch *channel) WriteFcall(ctx context.Context, fcall *Fcall) error {
select {
case <-ctx.Done():
return ctx.Err()
case <-ch.closed:
return ErrClosed
default:
}
deadline, ok := ctx.Deadline()
if !ok {
deadline = time.Now().Add(defaultRWTimeout)
}
if err := ch.conn.SetWriteDeadline(deadline); err != nil {
log.Printf("transport: error setting read deadline on %v: %v", ch.conn.RemoteAddr(), err)
}
if err := ch.maybeTruncate(fcall); err != nil {
return err
}
p, err := ch.codec.Marshal(fcall)
if err != nil {
return err
}
if err := sendmsg(ch.bwr, p); err != nil {
return err
}
return ch.bwr.Flush()
}
// maybeTruncate will truncate the message to fit into msize on the wire, if
// possible, or modify the message to ensure the response won't overflow.
//
// If the message cannot be truncated, an error will be returned and the
// message should not be sent.
//
// A nil return value means the message can be sent without
func (ch *channel) maybeTruncate(fcall *Fcall) error {
// for certain message types, just remove the extra bytes from the data portion.
switch msg := fcall.Message.(type) {
// TODO(stevvooe): There is one more problematic message type:
//
// Rread: while we can employ the same truncation fix as Twrite, we
// need to make it observable to upstream handlers.
case MessageTread:
// We can rewrite msg.Count so that a return message will be under
// msize. This is more defensive than anything but will ensure that
// calls don't fail on sloppy servers.
// first, craft the shape of the response message
resp := newFcall(fcall.Tag, MessageRread{})
overflow := uint32(ch.msgmsize(resp)) + msg.Count - uint32(ch.msize)
if msg.Count < overflow {
// Let the bad thing happen; msize too small to even support valid
// rewrite. This will result in a Terror from the server-side or
// just work.
return nil
}
msg.Count -= overflow
fcall.Message = msg
return nil
case MessageTwrite:
// If we are going to overflow the msize, we need to truncate the write to
// appropriate size or throw an error in all other conditions.
size := ch.msgmsize(fcall)
if size <= ch.msize {
return nil
}
// overflow the msize, including the channel message size fields.
overflow := size - ch.msize
if len(msg.Data) < overflow {
// paranoid: if msg.Data is not big enough to handle the
// overflow, we should get an overflow error. MSize would have
// to be way too small to be realistic.
return overflowErr{size: overflow}
}
// The truncation is reflected in the return message (Rwrite) by
// the server, so we don't need a return value or error condition
// to communicate it.
msg.Data = msg.Data[:len(msg.Data)-overflow]
fcall.Message = msg // since we have a local copy
return nil
default:
size := ch.msgmsize(fcall)
if size > ch.msize {
// overflow the msize, including the channel message size fields.
return overflowErr{size: size - ch.msize}
}
return nil
}
}
// msgmsize returns the on-wire msize of the Fcall, including the size header.
// Typically, this can be used to detect whether or not the message overflows
// the msize buffer.
func (ch *channel) msgmsize(fcall *Fcall) int {
return channelMessageHeaderSize + ch.codec.Size(fcall)
}
// readmsg reads a 9p message into p from rd, ensuring that all bytes are
// consumed from the size header. If the size header indicates the message is
// larger than p, the entire message will be discarded, leaving a truncated
// portion in p. Any error should be treated as a framing error unless n is
// zero. The caller must check that n is less than or equal to len(p) to
// ensure that a valid message has been read.
func readmsg(rd io.Reader, p []byte) (n int, err error) {
var msize uint32
if err := binary.Read(rd, binary.LittleEndian, &msize); err != nil {
return 0, err
}
n += binary.Size(msize)
mbody := int(msize) - 4
if mbody < len(p) {
p = p[:mbody]
}
np, err := io.ReadFull(rd, p)
if err != nil {
return np + n, err
}
n += np
if mbody > len(p) {
// message has been read up to len(p) but we must consume the entire
// message. This is an error condition but is non-fatal if we can
// consume msize bytes.
nn, err := io.CopyN(ioutil.Discard, rd, int64(mbody-len(p)))
n += int(nn)
if err != nil {
return n, err
}
}
return n, nil
}
// sendmsg writes a message of len(p) to wr with a 9p size header. All errors
// should be considered terminal.
func sendmsg(wr io.Writer, p []byte) error {
size := uint32(len(p) + 4) // message size plus 4-bytes for size.
if err := binary.Write(wr, binary.LittleEndian, size); err != nil {
return err
}
// This assume partial writes to wr aren't possible. Not sure if this
// valid. Matters during timeout retries.
if n, err := wr.Write(p); err != nil {
return err
} else if n < len(p) {
return io.ErrShortWrite
}
return nil
}

View File

@ -1,253 +0,0 @@
package p9p
import (
"io"
"net"
"context"
)
type client struct {
version string
msize int
ctx context.Context
transport roundTripper
}
// NewSession returns a session using the connection. The Context ctx provides
// a context for out of bad messages, such as flushes, that may be sent by the
// session. The session can effectively shutdown with this context.
func NewSession(ctx context.Context, conn net.Conn) (Session, error) {
ch := newChannel(conn, codec9p{}, DefaultMSize) // sets msize, effectively.
// negotiate the protocol version
version, err := clientnegotiate(ctx, ch, DefaultVersion)
if err != nil {
return nil, err
}
return &client{
version: version,
msize: ch.MSize(),
ctx: ctx,
transport: newTransport(ctx, ch),
}, nil
}
var _ Session = &client{}
func (c *client) Version() (int, string) {
return c.msize, c.version
}
func (c *client) Auth(ctx context.Context, afid Fid, uname, aname string) (Qid, error) {
m := MessageTauth{
Afid: afid,
Uname: uname,
Aname: aname,
}
resp, err := c.transport.send(ctx, m)
if err != nil {
return Qid{}, err
}
rauth, ok := resp.(MessageRauth)
if !ok {
return Qid{}, ErrUnexpectedMsg
}
return rauth.Qid, nil
}
func (c *client) Attach(ctx context.Context, fid, afid Fid, uname, aname string) (Qid, error) {
m := MessageTattach{
Fid: fid,
Afid: afid,
Uname: uname,
Aname: aname,
}
resp, err := c.transport.send(ctx, m)
if err != nil {
return Qid{}, err
}
rattach, ok := resp.(MessageRattach)
if !ok {
return Qid{}, ErrUnexpectedMsg
}
return rattach.Qid, nil
}
func (c *client) Clunk(ctx context.Context, fid Fid) error {
resp, err := c.transport.send(ctx, MessageTclunk{
Fid: fid,
})
if err != nil {
return err
}
_, ok := resp.(MessageRclunk)
if !ok {
return ErrUnexpectedMsg
}
return nil
}
func (c *client) Remove(ctx context.Context, fid Fid) error {
resp, err := c.transport.send(ctx, MessageTremove{
Fid: fid,
})
if err != nil {
return err
}
_, ok := resp.(MessageRremove)
if !ok {
return ErrUnexpectedMsg
}
return nil
}
func (c *client) Walk(ctx context.Context, fid Fid, newfid Fid, names ...string) ([]Qid, error) {
if len(names) > 16 {
return nil, ErrWalkLimit
}
resp, err := c.transport.send(ctx, MessageTwalk{
Fid: fid,
Newfid: newfid,
Wnames: names,
})
if err != nil {
return nil, err
}
rwalk, ok := resp.(MessageRwalk)
if !ok {
return nil, ErrUnexpectedMsg
}
return rwalk.Qids, nil
}
func (c *client) Read(ctx context.Context, fid Fid, p []byte, offset int64) (n int, err error) {
resp, err := c.transport.send(ctx, MessageTread{
Fid: fid,
Offset: uint64(offset),
Count: uint32(len(p)),
})
if err != nil {
return 0, err
}
rread, ok := resp.(MessageRread)
if !ok {
return 0, ErrUnexpectedMsg
}
n = copy(p, rread.Data)
switch {
case len(rread.Data) == 0:
err = io.EOF
case n < len(p):
// TODO(stevvooe): Technically, we should treat this as an io.EOF.
// However, we cannot tell if the short read was due to EOF or due to
// truncation.
}
return n, err
}
func (c *client) Write(ctx context.Context, fid Fid, p []byte, offset int64) (n int, err error) {
resp, err := c.transport.send(ctx, MessageTwrite{
Fid: fid,
Offset: uint64(offset),
Data: p,
})
if err != nil {
return 0, err
}
rwrite, ok := resp.(MessageRwrite)
if !ok {
return 0, ErrUnexpectedMsg
}
if int(rwrite.Count) < len(p) {
err = io.ErrShortWrite
}
return int(rwrite.Count), err
}
func (c *client) Open(ctx context.Context, fid Fid, mode Flag) (Qid, uint32, error) {
resp, err := c.transport.send(ctx, MessageTopen{
Fid: fid,
Mode: mode,
})
if err != nil {
return Qid{}, 0, err
}
ropen, ok := resp.(MessageRopen)
if !ok {
return Qid{}, 0, ErrUnexpectedMsg
}
return ropen.Qid, ropen.IOUnit, nil
}
func (c *client) Create(ctx context.Context, parent Fid, name string, perm uint32, mode Flag) (Qid, uint32, error) {
resp, err := c.transport.send(ctx, MessageTcreate{
Fid: parent,
Name: name,
Perm: perm,
Mode: mode,
})
if err != nil {
return Qid{}, 0, err
}
rcreate, ok := resp.(MessageRcreate)
if !ok {
return Qid{}, 0, ErrUnexpectedMsg
}
return rcreate.Qid, rcreate.IOUnit, nil
}
func (c *client) Stat(ctx context.Context, fid Fid) (Dir, error) {
resp, err := c.transport.send(ctx, MessageTstat{Fid: fid})
if err != nil {
return Dir{}, err
}
rstat, ok := resp.(MessageRstat)
if !ok {
return Dir{}, ErrUnexpectedMsg
}
return rstat.Stat, nil
}
func (c *client) WStat(ctx context.Context, fid Fid, dir Dir) error {
resp, err := c.transport.send(ctx, MessageTwstat{
Fid: fid,
Stat: dir,
})
if err != nil {
return err
}
_, ok := resp.(MessageRwstat)
if !ok {
return ErrUnexpectedMsg
}
return nil
}

View File

@ -1,26 +0,0 @@
package p9p
import (
"context"
)
type contextKey string
const (
versionKey contextKey = "9p.version"
)
func withVersion(ctx context.Context, version string) context.Context {
return context.WithValue(ctx, versionKey, version)
}
// GetVersion returns the protocol version from the context. If the version is
// not known, an empty string is returned. This is typically set on the
// context passed into function calls in a server implementation.
func GetVersion(ctx context.Context) string {
v, ok := ctx.Value(versionKey).(string)
if !ok {
return ""
}
return v
}

View File

@ -1,133 +0,0 @@
package p9p
import "context"
// Handler defines an interface for 9p message handlers. A handler
// implementation could be used to intercept calls of all types before sending
// them to the next handler.
type Handler interface {
Handle(ctx context.Context, msg Message) (Message, error)
// TODO(stevvooe): Right now, this interface is functianally identical to
// roundtripper. If we find that this is sufficient on the server-side, we
// may unify the types. For now, we leave them separated to differentiate
// between them.
}
// HandlerFunc is a convenience type for defining inline handlers.
type HandlerFunc func(ctx context.Context, msg Message) (Message, error)
// Handle implements the requirements for the Handler interface.
func (fn HandlerFunc) Handle(ctx context.Context, msg Message) (Message, error) {
return fn(ctx, msg)
}
// Dispatch returns a handler that dispatches messages to the target session.
// No concurrency is managed by the returned handler. It simply turns messages
// into function calls on the session.
func Dispatch(session Session) Handler {
return HandlerFunc(func(ctx context.Context, msg Message) (Message, error) {
switch msg := msg.(type) {
case MessageTauth:
qid, err := session.Auth(ctx, msg.Afid, msg.Uname, msg.Aname)
if err != nil {
return nil, err
}
return MessageRauth{Qid: qid}, nil
case MessageTattach:
qid, err := session.Attach(ctx, msg.Fid, msg.Afid, msg.Uname, msg.Aname)
if err != nil {
return nil, err
}
return MessageRattach{
Qid: qid,
}, nil
case MessageTwalk:
// TODO(stevvooe): This is one of the places where we need to manage
// fid allocation lifecycle. We need to reserve the fid, then, if this
// call succeeds, we should alloc the fid for future uses. Also need
// to interact correctly with concurrent clunk and the flush of this
// walk message.
qids, err := session.Walk(ctx, msg.Fid, msg.Newfid, msg.Wnames...)
if err != nil {
return nil, err
}
return MessageRwalk{
Qids: qids,
}, nil
case MessageTopen:
qid, iounit, err := session.Open(ctx, msg.Fid, msg.Mode)
if err != nil {
return nil, err
}
return MessageRopen{
Qid: qid,
IOUnit: iounit,
}, nil
case MessageTcreate:
qid, iounit, err := session.Create(ctx, msg.Fid, msg.Name, msg.Perm, msg.Mode)
if err != nil {
return nil, err
}
return MessageRcreate{
Qid: qid,
IOUnit: iounit,
}, nil
case MessageTread:
p := make([]byte, int(msg.Count))
n, err := session.Read(ctx, msg.Fid, p, int64(msg.Offset))
if err != nil {
return nil, err
}
return MessageRread{
Data: p[:n],
}, nil
case MessageTwrite:
n, err := session.Write(ctx, msg.Fid, msg.Data, int64(msg.Offset))
if err != nil {
return nil, err
}
return MessageRwrite{
Count: uint32(n),
}, nil
case MessageTclunk:
// TODO(stevvooe): Manage the clunking of file descriptors based on
// walk and attach call progression.
if err := session.Clunk(ctx, msg.Fid); err != nil {
return nil, err
}
return MessageRclunk{}, nil
case MessageTremove:
if err := session.Remove(ctx, msg.Fid); err != nil {
return nil, err
}
return MessageRremove{}, nil
case MessageTstat:
dir, err := session.Stat(ctx, msg.Fid)
if err != nil {
return nil, err
}
return MessageRstat{
Stat: dir,
}, nil
case MessageTwstat:
if err := session.WStat(ctx, msg.Fid, msg.Stat); err != nil {
return nil, err
}
return MessageRwstat{}, nil
default:
return nil, ErrUnknownMsg
}
})
}

View File

@ -1,78 +0,0 @@
/*
Package p9p implements a compliant 9P2000 client and server library for use
in modern, production Go services. This package differentiates itself in that
is has departed from the plan 9 implementation primitives and better follows
idiomatic Go style.
The package revolves around the session type, which is an enumeration of raw
9p message calls. A few calls, such as flush and version, have been elided,
defering their usage to the server implementation. Sessions can be trivially
proxied through clients and servers.
Getting Started
The best place to get started is with Serve. Serve can be provided a
connection and a handler. A typical implementation will call Serve as part of
a listen/accept loop. As each network connection is created, Serve can be
called with a handler for the specific connection. The handler can be
implemented with a Session via the Dispatch function or can generate sessions
for dispatch in response to client messages. (See cmd/9ps for an example)
On the client side, NewSession provides a 9p session from a connection. After
a version negotiation, methods can be called on the session, in parallel, and
calls will be sent over the connection. Call timeouts can be controlled via
the context provided to each method call.
Framework
This package has the beginning of a nice client-server framework for working
with 9p. Some of the abstractions aren't entirely fleshed out, but most of
this can center around the Handler.
Missing from this are a number of tools for implementing 9p servers. The most
glaring are directory read and walk helpers. Other, more complex additions
might be a system to manage in memory filesystem trees that expose multi-user
sessions.
Differences
The largest difference between this package and other 9p packages is
simplification of the types needed to implement a server. To avoid confusing
bugs and odd behavior, the components are separated by each level of the
protocol. One example is that requests and responses are separated and they no
longer hold mutable state. This means that framing, transport management,
encoding, and dispatching are componentized. Little work will be required to
swap out encodings, transports or connection implementations.
Context Integration
This package has been wired from top to bottom to support context-based
resource management. Everything from startup to shutdown can have timeouts
using contexts. Not all close methods are fully in place, but we are very
close to having controlled, predictable cleanup for both servers and clients.
Timeouts can be very granular or very course, depending on the context of the
timeout. For example, it is very easy to set a short timeout for a stat call
but a long timeout for reading data.
Multiversion Support
Currently, there is not multiversion support. The hooks and functionality are
in place to add multi-version support. Generally, the correct space to do this
is in the codec. Types, such as Dir, simply need to be extended to support the
possibility of extra fields.
The real question to ask here is what is the role of the version number in the
9p protocol. It really comes down to the level of support required. Do we just
need it at the protocol level, or do handlers and sessions need to be have
differently based on negotiated versions?
Caveats
This package has a number of TODOs to make it easier to use. Most of the
existing code provides a solid base to work from. Don't be discouraged by the
sawdust.
In addition, the testing is embarassingly lacking. With time, we can get full
testing going and ensure we have confidence in the implementation.
*/
package p9p

View File

@ -1,564 +0,0 @@
package p9p
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"log"
"reflect"
"strings"
"time"
)
// Codec defines the interface for encoding and decoding of 9p types.
// Unsupported types will throw an error.
type Codec interface {
// Unmarshal from data into the value pointed to by v.
Unmarshal(data []byte, v interface{}) error
// Marshal the value v into a byte slice.
Marshal(v interface{}) ([]byte, error)
// Size returns the encoded size for the target of v.
Size(v interface{}) int
}
// NewCodec returns a new, standard 9P2000 codec, ready for use.
func NewCodec() Codec {
return codec9p{}
}
type codec9p struct{}
func (c codec9p) Unmarshal(data []byte, v interface{}) error {
dec := &decoder{bytes.NewReader(data)}
return dec.decode(v)
}
func (c codec9p) Marshal(v interface{}) ([]byte, error) {
var b bytes.Buffer
enc := &encoder{&b}
if err := enc.encode(v); err != nil {
return nil, err
}
return b.Bytes(), nil
}
func (c codec9p) Size(v interface{}) int {
return int(size9p(v))
}
// DecodeDir decodes a directory entry from rd using the provided codec.
func DecodeDir(codec Codec, rd io.Reader, d *Dir) error {
var ll uint16
// pull the size off the wire
if err := binary.Read(rd, binary.LittleEndian, &ll); err != nil {
return err
}
p := make([]byte, ll+2)
binary.LittleEndian.PutUint16(p, ll) // must have size at start
// read out the rest of the record
if _, err := io.ReadFull(rd, p[2:]); err != nil {
return err
}
return codec.Unmarshal(p, d)
}
// EncodeDir writes the directory to wr.
func EncodeDir(codec Codec, wr io.Writer, d *Dir) error {
p, err := codec.Marshal(d)
if err != nil {
return err
}
_, err = wr.Write(p)
return err
}
type encoder struct {
wr io.Writer
}
func (e *encoder) encode(vs ...interface{}) error {
for _, v := range vs {
switch v := v.(type) {
case uint8, uint16, uint32, uint64, FcallType, Tag, QType, Fid, Flag,
*uint8, *uint16, *uint32, *uint64, *FcallType, *Tag, *QType, *Fid, *Flag:
if err := binary.Write(e.wr, binary.LittleEndian, v); err != nil {
return err
}
case []byte:
if err := e.encode(uint32(len(v))); err != nil {
return err
}
if err := binary.Write(e.wr, binary.LittleEndian, v); err != nil {
return err
}
case *[]byte:
if err := e.encode(*v); err != nil {
return err
}
case string:
if err := binary.Write(e.wr, binary.LittleEndian, uint16(len(v))); err != nil {
return err
}
_, err := io.WriteString(e.wr, v)
if err != nil {
return err
}
case *string:
if err := e.encode(*v); err != nil {
return err
}
case []string:
if err := e.encode(uint16(len(v))); err != nil {
return err
}
for _, m := range v {
if err := e.encode(m); err != nil {
return err
}
}
case *[]string:
if err := e.encode(*v); err != nil {
return err
}
case time.Time:
if err := e.encode(uint32(v.Unix())); err != nil {
return err
}
case *time.Time:
if err := e.encode(*v); err != nil {
return err
}
case Qid:
if err := e.encode(v.Type, v.Version, v.Path); err != nil {
return err
}
case *Qid:
if err := e.encode(*v); err != nil {
return err
}
case []Qid:
if err := e.encode(uint16(len(v))); err != nil {
return err
}
elements := make([]interface{}, len(v))
for i := range v {
elements[i] = &v[i]
}
if err := e.encode(elements...); err != nil {
return err
}
case *[]Qid:
if err := e.encode(*v); err != nil {
return err
}
case Dir:
elements, err := fields9p(v)
if err != nil {
return err
}
if err := e.encode(uint16(size9p(elements...))); err != nil {
return err
}
if err := e.encode(elements...); err != nil {
return err
}
case *Dir:
if err := e.encode(*v); err != nil {
return err
}
case []Dir:
elements := make([]interface{}, len(v))
for i := range v {
elements[i] = &v[i]
}
if err := e.encode(elements...); err != nil {
return err
}
case *[]Dir:
if err := e.encode(*v); err != nil {
return err
}
case Fcall:
if err := e.encode(v.Type, v.Tag, v.Message); err != nil {
return err
}
case *Fcall:
if err := e.encode(*v); err != nil {
return err
}
case Message:
elements, err := fields9p(v)
if err != nil {
return err
}
switch v.(type) {
case MessageRstat, *MessageRstat:
// NOTE(stevvooe): Prepend size preceeding Dir. See bugs in
// http://man.cat-v.org/plan_9/5/stat to make sense of this.
// The field has been included here but we need to make sure
// to double emit it for Rstat.
if err := e.encode(uint16(size9p(elements...))); err != nil {
return err
}
}
if err := e.encode(elements...); err != nil {
return err
}
}
}
return nil
}
type decoder struct {
rd io.Reader
}
// read9p extracts values from rd and unmarshals them to the targets of vs.
func (d *decoder) decode(vs ...interface{}) error {
for _, v := range vs {
switch v := v.(type) {
case *uint8, *uint16, *uint32, *uint64, *FcallType, *Tag, *QType, *Fid, *Flag:
if err := binary.Read(d.rd, binary.LittleEndian, v); err != nil {
return err
}
case *[]byte:
var ll uint32
if err := d.decode(&ll); err != nil {
return err
}
if ll > 0 {
*v = make([]byte, int(ll))
}
if err := binary.Read(d.rd, binary.LittleEndian, v); err != nil {
return err
}
case *string:
var ll uint16
// implement string[s] encoding
if err := d.decode(&ll); err != nil {
return err
}
b := make([]byte, ll)
n, err := io.ReadFull(d.rd, b)
if err != nil {
return err
}
if n != int(ll) {
return fmt.Errorf("unexpected string length")
}
*v = string(b)
case *[]string:
var ll uint16
if err := d.decode(&ll); err != nil {
return err
}
elements := make([]interface{}, int(ll))
*v = make([]string, int(ll))
for i := range elements {
elements[i] = &(*v)[i]
}
if err := d.decode(elements...); err != nil {
return err
}
case *time.Time:
var epoch uint32
if err := d.decode(&epoch); err != nil {
return err
}
*v = time.Unix(int64(epoch), 0).UTC()
case *Qid:
if err := d.decode(&v.Type, &v.Version, &v.Path); err != nil {
return err
}
case *[]Qid:
var ll uint16
if err := d.decode(&ll); err != nil {
return err
}
elements := make([]interface{}, int(ll))
*v = make([]Qid, int(ll))
for i := range elements {
elements[i] = &(*v)[i]
}
if err := d.decode(elements...); err != nil {
return err
}
case *Dir:
var ll uint16
if err := d.decode(&ll); err != nil {
return err
}
b := make([]byte, ll)
// must consume entire dir entry.
n, err := io.ReadFull(d.rd, b)
if err != nil {
log.Println("dir readfull failed:", err, ll, n)
return err
}
elements, err := fields9p(v)
if err != nil {
return err
}
dec := &decoder{bytes.NewReader(b)}
if err := dec.decode(elements...); err != nil {
return err
}
case *[]Dir:
*v = make([]Dir, 0)
for {
element := Dir{}
if err := d.decode(&element); err != nil {
if err == io.EOF {
return nil
}
return err
}
*v = append(*v, element)
}
case *Fcall:
if err := d.decode(&v.Type, &v.Tag); err != nil {
return err
}
message, err := newMessage(v.Type)
if err != nil {
return err
}
// NOTE(stevvooe): We do a little pointer dance to allocate the
// new type, write to it, then assign it back to the interface as
// a concrete type, avoiding a pointer (the interface) to a
// pointer.
rv := reflect.New(reflect.TypeOf(message))
if err := d.decode(rv.Interface()); err != nil {
return err
}
v.Message = rv.Elem().Interface().(Message)
case Message:
elements, err := fields9p(v)
if err != nil {
return err
}
switch v.(type) {
case *MessageRstat, MessageRstat:
// NOTE(stevvooe): Consume extra size preceeding Dir. See bugs
// in http://man.cat-v.org/plan_9/5/stat to make sense of
// this. The field has been included here but we need to make
// sure to double emit it for Rstat. decode extra size header
// for stat structure.
var ll uint16
if err := d.decode(&ll); err != nil {
return err
}
}
if err := d.decode(elements...); err != nil {
return err
}
}
}
return nil
}
// size9p calculates the projected size of the values in vs when encoded into
// 9p binary protocol. If an element or elements are not valid for 9p encoded,
// the value 0 will be used for the size. The error will be detected when
// encoding.
func size9p(vs ...interface{}) uint32 {
var s uint32
for _, v := range vs {
if v == nil {
continue
}
switch v := v.(type) {
case uint8, uint16, uint32, uint64, FcallType, Tag, QType, Fid, Flag,
*uint8, *uint16, *uint32, *uint64, *FcallType, *Tag, *QType, *Fid, *Flag:
s += uint32(binary.Size(v))
case []byte:
s += uint32(binary.Size(uint32(0)) + len(v))
case *[]byte:
s += size9p(uint32(0), *v)
case string:
s += uint32(binary.Size(uint16(0)) + len(v))
case *string:
s += size9p(*v)
case []string:
s += size9p(uint16(0))
for _, sv := range v {
s += size9p(sv)
}
case *[]string:
s += size9p(*v)
case time.Time, *time.Time:
// BUG(stevvooe): Y2038 is coming.
s += size9p(uint32(0))
case Qid:
s += size9p(v.Type, v.Version, v.Path)
case *Qid:
s += size9p(*v)
case []Qid:
s += size9p(uint16(0))
elements := make([]interface{}, len(v))
for i := range elements {
elements[i] = &v[i]
}
s += size9p(elements...)
case *[]Qid:
s += size9p(*v)
case Dir:
// walk the fields of the message to get the total size. we just
// use the field order from the message struct. We may add tag
// ignoring if needed.
elements, err := fields9p(v)
if err != nil {
// BUG(stevvooe): The options here are to return 0, panic or
// make this return an error. Ideally, we make it safe to
// return 0 and have the rest of the package do the right
// thing. For now, we do this, but may want to panic until
// things are stable.
panic(err)
}
s += size9p(elements...) + size9p(uint16(0))
case *Dir:
s += size9p(*v)
case []Dir:
elements := make([]interface{}, len(v))
for i := range elements {
elements[i] = &v[i]
}
s += size9p(elements...)
case *[]Dir:
s += size9p(*v)
case Fcall:
s += size9p(v.Type, v.Tag, v.Message)
case *Fcall:
s += size9p(*v)
case Message:
// special case twstat and rstat for size fields. See bugs in
// http://man.cat-v.org/plan_9/5/stat to make sense of this.
switch v.(type) {
case *MessageRstat, MessageRstat:
s += size9p(uint16(0)) // for extra size field before dir
}
// walk the fields of the message to get the total size. we just
// use the field order from the message struct. We may add tag
// ignoring if needed.
elements, err := fields9p(v)
if err != nil {
// BUG(stevvooe): The options here are to return 0, panic or
// make this return an error. Ideally, we make it safe to
// return 0 and have the rest of the package do the right
// thing. For now, we do this, but may want to panic until
// things are stable.
panic(err)
}
s += size9p(elements...)
}
}
return s
}
// fields9p lists the settable fields from a struct type for reading and
// writing. We are using a lot of reflection here for fairly static
// serialization but we can replace this in the future with generated code if
// performance is an issue.
func fields9p(v interface{}) ([]interface{}, error) {
rv := reflect.Indirect(reflect.ValueOf(v))
if rv.Kind() != reflect.Struct {
return nil, fmt.Errorf("cannot extract fields from non-struct: %v", rv)
}
var elements []interface{}
for i := 0; i < rv.NumField(); i++ {
f := rv.Field(i)
if !f.CanInterface() {
// unexported field, skip it.
continue
}
if f.CanAddr() {
f = f.Addr()
}
elements = append(elements, f.Interface())
}
return elements, nil
}
func string9p(v interface{}) string {
if v == nil {
return "nil"
}
rv := reflect.Indirect(reflect.ValueOf(v))
if rv.Kind() != reflect.Struct {
panic("not a struct")
}
var s string
for i := 0; i < rv.NumField(); i++ {
f := rv.Field(i)
s += fmt.Sprintf(" %v=%v", strings.ToLower(rv.Type().Field(i).Name), f.Interface())
}
return s
}

View File

@ -1,58 +0,0 @@
package p9p
import (
"errors"
"fmt"
)
// MessageRerror provides both a Go error type and message type.
type MessageRerror struct {
Ename string
}
// 9p wire errors returned by Session interface methods
var (
ErrBadattach = new9pError("unknown specifier in attach")
ErrBadoffset = new9pError("bad offset")
ErrBadcount = new9pError("bad count")
ErrBotch = new9pError("9P protocol botch")
ErrCreatenondir = new9pError("create in non-directory")
ErrDupfid = new9pError("duplicate fid")
ErrDuptag = new9pError("duplicate tag")
ErrIsdir = new9pError("is a directory")
ErrNocreate = new9pError("create prohibited")
ErrNomem = new9pError("out of memory")
ErrNoremove = new9pError("remove prohibited")
ErrNostat = new9pError("stat prohibited")
ErrNotfound = new9pError("file not found")
ErrNowrite = new9pError("write prohibited")
ErrNowstat = new9pError("wstat prohibited")
ErrPerm = new9pError("permission denied")
ErrUnknownfid = new9pError("unknown fid")
ErrBaddir = new9pError("bad directory in wstat")
ErrWalknodir = new9pError("walk in non-directory")
// extra errors not part of the normal protocol
ErrTimeout = new9pError("fcall timeout") // returned when timing out on the fcall
ErrUnknownTag = new9pError("unknown tag")
ErrUnknownMsg = new9pError("unknown message") // returned when encountering unknown message type
ErrUnexpectedMsg = new9pError("unexpected message") // returned when an unexpected message is encountered
ErrWalkLimit = new9pError("too many wnames in walk")
ErrClosed = errors.New("closed")
)
// new9pError returns a new 9p error ready for the wire.
func new9pError(s string) error {
return MessageRerror{Ename: s}
}
// Type ensures that 9p errors can be transparently used as a 9p message in an
// Fcall.
func (MessageRerror) Type() FcallType {
return Rerror
}
func (e MessageRerror) Error() string {
return fmt.Sprintf("9p: %v", e.Ename)
}

View File

@ -1,142 +0,0 @@
package p9p
import "fmt"
// FcallType encodes the message type for the target Fcall.
type FcallType uint8
// Definitions for Fcall's used in 9P2000.
const (
Tversion FcallType = iota + 100
Rversion
Tauth
Rauth
Tattach
Rattach
Terror
Rerror
Tflush
Rflush
Twalk
Rwalk
Topen
Ropen
Tcreate
Rcreate
Tread
Rread
Twrite
Rwrite
Tclunk
Rclunk
Tremove
Rremove
Tstat
Rstat
Twstat
Rwstat
Tmax
)
func (fct FcallType) String() string {
switch fct {
case Tversion:
return "Tversion"
case Rversion:
return "Rversion"
case Tauth:
return "Tauth"
case Rauth:
return "Rauth"
case Tattach:
return "Tattach"
case Rattach:
return "Rattach"
case Terror:
// invalid.
return "Terror"
case Rerror:
return "Rerror"
case Tflush:
return "Tflush"
case Rflush:
return "Rflush"
case Twalk:
return "Twalk"
case Rwalk:
return "Rwalk"
case Topen:
return "Topen"
case Ropen:
return "Ropen"
case Tcreate:
return "Tcreate"
case Rcreate:
return "Rcreate"
case Tread:
return "Tread"
case Rread:
return "Rread"
case Twrite:
return "Twrite"
case Rwrite:
return "Rwrite"
case Tclunk:
return "Tclunk"
case Rclunk:
return "Rclunk"
case Tremove:
return "Tremove"
case Rremove:
return "Rremove"
case Tstat:
return "Tstat"
case Rstat:
return "Rstat"
case Twstat:
return "Twstat"
case Rwstat:
return "Rwstat"
default:
return "Tunknown"
}
}
// Fcall defines the fields for sending a 9p formatted message. The type will
// be introspected from the Message implementation.
type Fcall struct {
Type FcallType
Tag Tag
Message Message
}
func newFcall(tag Tag, msg Message) *Fcall {
return &Fcall{
Type: msg.Type(),
Tag: tag,
Message: msg,
}
}
func newErrorFcall(tag Tag, err error) *Fcall {
var msg Message
switch v := err.(type) {
case MessageRerror:
msg = v
case *MessageRerror:
msg = *v
default:
msg = MessageRerror{Ename: v.Error()}
}
return &Fcall{
Type: Rerror,
Tag: tag,
Message: msg,
}
}
func (fc *Fcall) String() string {
return fmt.Sprintf("%v(%v) %v", fc.Type, fc.Tag, string9p(fc.Message))
}

View File

@ -1,216 +0,0 @@
package p9p
import "fmt"
// Message represents the target of an fcall.
type Message interface {
// Type returns the type of call for the target message.
Type() FcallType
}
// newMessage returns a new instance of the message based on the Fcall type.
func newMessage(typ FcallType) (Message, error) {
switch typ {
case Tversion:
return MessageTversion{}, nil
case Rversion:
return MessageRversion{}, nil
case Tauth:
return MessageTauth{}, nil
case Rauth:
return MessageRauth{}, nil
case Tattach:
return MessageTattach{}, nil
case Rattach:
return MessageRattach{}, nil
case Rerror:
return MessageRerror{}, nil
case Tflush:
return MessageTflush{}, nil
case Rflush:
return MessageRflush{}, nil // No message body for this response.
case Twalk:
return MessageTwalk{}, nil
case Rwalk:
return MessageRwalk{}, nil
case Topen:
return MessageTopen{}, nil
case Ropen:
return MessageRopen{}, nil
case Tcreate:
return MessageTcreate{}, nil
case Rcreate:
return MessageRcreate{}, nil
case Tread:
return MessageTread{}, nil
case Rread:
return MessageRread{}, nil
case Twrite:
return MessageTwrite{}, nil
case Rwrite:
return MessageRwrite{}, nil
case Tclunk:
return MessageTclunk{}, nil
case Rclunk:
return MessageRclunk{}, nil // no response body
case Tremove:
return MessageTremove{}, nil
case Rremove:
return MessageRremove{}, nil
case Tstat:
return MessageTstat{}, nil
case Rstat:
return MessageRstat{}, nil
case Twstat:
return MessageTwstat{}, nil
case Rwstat:
return MessageRwstat{}, nil
}
return nil, fmt.Errorf("unknown message type")
}
// MessageVersion encodes the message body for Tversion and Rversion RPC
// calls. The body is identical in both directions.
type MessageTversion struct {
MSize uint32
Version string
}
type MessageRversion struct {
MSize uint32
Version string
}
type MessageTauth struct {
Afid Fid
Uname string
Aname string
}
type MessageRauth struct {
Qid Qid
}
type MessageTflush struct {
Oldtag Tag
}
type MessageRflush struct{}
type MessageTattach struct {
Fid Fid
Afid Fid
Uname string
Aname string
}
type MessageRattach struct {
Qid Qid
}
type MessageTwalk struct {
Fid Fid
Newfid Fid
Wnames []string
}
type MessageRwalk struct {
Qids []Qid
}
type MessageTopen struct {
Fid Fid
Mode Flag
}
type MessageRopen struct {
Qid Qid
IOUnit uint32
}
type MessageTcreate struct {
Fid Fid
Name string
Perm uint32
Mode Flag
}
type MessageRcreate struct {
Qid Qid
IOUnit uint32
}
type MessageTread struct {
Fid Fid
Offset uint64
Count uint32
}
type MessageRread struct {
Data []byte
}
type MessageTwrite struct {
Fid Fid
Offset uint64
Data []byte
}
type MessageRwrite struct {
Count uint32
}
type MessageTclunk struct {
Fid Fid
}
type MessageRclunk struct{}
type MessageTremove struct {
Fid Fid
}
type MessageRremove struct{}
type MessageTstat struct {
Fid Fid
}
type MessageRstat struct {
Stat Dir
}
type MessageTwstat struct {
Fid Fid
Stat Dir
}
type MessageRwstat struct{}
func (MessageTversion) Type() FcallType { return Tversion }
func (MessageRversion) Type() FcallType { return Rversion }
func (MessageTauth) Type() FcallType { return Tauth }
func (MessageRauth) Type() FcallType { return Rauth }
func (MessageTflush) Type() FcallType { return Tflush }
func (MessageRflush) Type() FcallType { return Rflush }
func (MessageTattach) Type() FcallType { return Tattach }
func (MessageRattach) Type() FcallType { return Rattach }
func (MessageTwalk) Type() FcallType { return Twalk }
func (MessageRwalk) Type() FcallType { return Rwalk }
func (MessageTopen) Type() FcallType { return Topen }
func (MessageRopen) Type() FcallType { return Ropen }
func (MessageTcreate) Type() FcallType { return Tcreate }
func (MessageRcreate) Type() FcallType { return Rcreate }
func (MessageTread) Type() FcallType { return Tread }
func (MessageRread) Type() FcallType { return Rread }
func (MessageTwrite) Type() FcallType { return Twrite }
func (MessageRwrite) Type() FcallType { return Rwrite }
func (MessageTclunk) Type() FcallType { return Tclunk }
func (MessageRclunk) Type() FcallType { return Rclunk }
func (MessageTremove) Type() FcallType { return Tremove }
func (MessageRremove) Type() FcallType { return Rremove }
func (MessageTstat) Type() FcallType { return Tstat }
func (MessageRstat) Type() FcallType { return Rstat }
func (MessageTwstat) Type() FcallType { return Twstat }
func (MessageRwstat) Type() FcallType { return Rwstat }

View File

@ -1,49 +0,0 @@
package p9p
import "fmt"
// Overflow will return a positive number, indicating there was an overflow for
// the error.
func Overflow(err error) int {
if of, ok := err.(overflow); ok {
return of.Size()
}
// traverse cause, if above fails.
if causal, ok := err.(interface {
Cause() error
}); ok {
return Overflow(causal.Cause())
}
return 0
}
// overflow is a resolvable error type that can help callers negotiate
// session msize. If this error is encountered, no message was sent.
//
// The return value of `Size()` represents the number of bytes that would have
// been truncated if the message were sent. This IS NOT the optimal buffer size
// for operations like read and write.
//
// In the case of `Twrite`, the caller can Size() from the local size to get an
// optimally size buffer or the write can simply be truncated to `len(buf) -
// err.Size()`.
//
// For the most part, no users of this package should see this error in
// practice. If this escapes the Session interface, it is a bug.
type overflow interface {
Size() int // number of bytes overflowed.
}
type overflowErr struct {
size int // number of bytes overflowed
}
func (o overflowErr) Error() string {
return fmt.Sprintf("message overflowed %d bytes", o.size)
}
func (o overflowErr) Size() int {
return o.size
}

View File

@ -1,93 +0,0 @@
package p9p
import (
"io"
"context"
)
// ReaddirAll reads all the directory entries for the resource fid.
func ReaddirAll(session Session, fid Fid) ([]Dir, error) {
panic("not implemented")
}
// Readdir helps one to implement the server-side of Session.Read on
// directories.
type Readdir struct {
nextfn func() (Dir, error)
buf *Dir // one-item buffer
codec Codec
offset int64
}
// NewReaddir returns a new Readdir to assist implementing server-side Readdir.
// The codec will be used to decode messages with Dir entries. The provided
// function next will be called until io.EOF is returned.
func NewReaddir(codec Codec, next func() (Dir, error)) *Readdir {
return &Readdir{
nextfn: next,
codec: codec,
}
}
// NewFixedReaddir returns a Readdir that will returned a fixed set of
// directory entries.
func NewFixedReaddir(codec Codec, dir []Dir) *Readdir {
dirs := make([]Dir, len(dir))
copy(dirs, dir) // make our own copy!
return NewReaddir(codec,
func() (Dir, error) {
if len(dirs) == 0 {
return Dir{}, io.EOF
}
d := dirs[0]
dirs = dirs[1:]
return d, nil
})
}
func (rd *Readdir) Read(ctx context.Context, p []byte, offset int64) (n int, err error) {
if rd.offset != offset {
return 0, ErrBadoffset
}
p = p[:0:len(p)]
for len(p) < cap(p) {
var d Dir
if rd.buf != nil {
d = *rd.buf
rd.buf = nil
} else {
d, err = rd.nextfn()
if err != nil {
goto done
}
}
var dp []byte
dp, err = rd.codec.Marshal(d)
if err != nil {
goto done
}
if len(p)+len(dp) > cap(p) {
// will over fill buffer. save item and exit.
rd.buf = &d
goto done
}
p = append(p, dp...)
}
done:
if err == io.EOF && len(p) > 0 {
// Don't let io.EOF escape if we've written to p. 9p doesn't handle
// this like Go.
err = nil
}
rd.offset += int64(len(p))
return len(p), err
}

View File

@ -1,257 +0,0 @@
package p9p
import (
"fmt"
"log"
"net"
"sync"
"time"
"context"
)
// TODO(stevvooe): Add net/http.Server-like type here to manage connections.
// Coupled with Handler mux, we can get a very http-like experience for 9p
// servers.
// ServeConn the 9p handler over the provided network connection.
func ServeConn(ctx context.Context, cn net.Conn, handler Handler) error {
// TODO(stevvooe): It would be nice if the handler could declare the
// supported version. Before we had handler, we used the session to get
// the version (msize, version := session.Version()). We must decided if
// we want to proxy version and message size decisions all the back to the
// origin server or make those decisions at each link of a proxy chain.
ch := newChannel(cn, codec9p{}, DefaultMSize)
negctx, cancel := context.WithTimeout(ctx, 1*time.Second)
defer cancel()
// TODO(stevvooe): For now, we negotiate here. It probably makes sense to
// do this outside of this function and then pass in a ready made channel.
// We are not really ready to export the channel type yet.
if err := servernegotiate(negctx, ch, DefaultVersion); err != nil {
// TODO(stevvooe): Need better error handling and retry support here.
return fmt.Errorf("error negotiating version: %s", err)
}
ctx = withVersion(ctx, DefaultVersion)
c := &conn{
ctx: ctx,
ch: ch,
handler: handler,
closed: make(chan struct{}),
}
return c.serve()
}
// conn plays role of session dispatch for handler in a server.
type conn struct {
ctx context.Context
session Session
ch Channel
handler Handler
once sync.Once
closed chan struct{}
err error // terminal error for the conn
}
// activeRequest includes information about the active request.
type activeRequest struct {
ctx context.Context
request *Fcall
cancel context.CancelFunc
}
// serve messages on the connection until an error is encountered.
func (c *conn) serve() error {
tags := map[Tag]*activeRequest{} // active requests
requests := make(chan *Fcall) // sync, read-limited
responses := make(chan *Fcall) // sync, goroutine consumed
completed := make(chan *Fcall) // sync, send in goroutine per request
// read loop
go c.read(requests)
go c.write(responses)
log.Println("server.run()")
for {
select {
case req := <-requests:
if _, ok := tags[req.Tag]; ok {
select {
case responses <- newErrorFcall(req.Tag, ErrDuptag):
// Send to responses, bypass tag management.
case <-c.ctx.Done():
return c.ctx.Err()
case <-c.closed:
return c.err
}
continue
}
switch msg := req.Message.(type) {
case MessageTflush:
log.Println("server: flushing message", msg.Oldtag)
var resp *Fcall
// check if we have actually know about the requested flush
active, ok := tags[msg.Oldtag]
if ok {
active.cancel() // propagate cancellation to callees
delete(tags, msg.Oldtag)
resp = newFcall(req.Tag, MessageRflush{})
} else {
resp = newErrorFcall(req.Tag, ErrUnknownTag)
}
select {
case responses <- resp:
// bypass tag management in completed.
case <-c.ctx.Done():
return c.ctx.Err()
case <-c.closed:
return c.err
}
default:
// Allows us to session handlers to cancel processing of the fcall
// through context.
ctx, cancel := context.WithCancel(c.ctx)
// The contents of these instances are only writable in the main
// server loop. The value of tag will not change.
tags[req.Tag] = &activeRequest{
ctx: ctx,
request: req,
cancel: cancel,
}
go func(ctx context.Context, req *Fcall) {
// TODO(stevvooe): Re-write incoming Treads so that handler
// can always respond with a message of the correct msize.
var resp *Fcall
msg, err := c.handler.Handle(ctx, req.Message)
if err != nil {
// all handler errors are forwarded as protocol errors.
resp = newErrorFcall(req.Tag, err)
} else {
resp = newFcall(req.Tag, msg)
}
select {
case completed <- resp:
case <-ctx.Done():
return
case <-c.closed:
return
}
}(ctx, req)
}
case resp := <-completed:
// only responses that flip the tag state traverse this section.
active, ok := tags[resp.Tag]
if !ok {
// The tag is no longer active. Likely a flushed message.
continue
}
select {
case responses <- resp:
case <-active.ctx.Done():
// the context was canceled for some reason, perhaps timeout or
// due to a flush call. We treat this as a condition where a
// response should not be sent.
log.Println("canceled", resp, active.ctx.Err())
}
delete(tags, resp.Tag)
case <-c.ctx.Done():
return c.ctx.Err()
case <-c.closed:
return c.err
}
}
}
// read takes requests off the channel and sends them on requests.
func (c *conn) read(requests chan *Fcall) {
for {
req := new(Fcall)
if err := c.ch.ReadFcall(c.ctx, req); err != nil {
if err, ok := err.(net.Error); ok {
if err.Timeout() || err.Temporary() {
// TODO(stevvooe): A full idle timeout on the connection
// should be enforced here. No logging because it is quite
// chatty.
continue
}
}
c.CloseWithError(fmt.Errorf("error reading fcall: %v", err))
return
}
select {
case requests <- req:
case <-c.ctx.Done():
c.CloseWithError(c.ctx.Err())
return
case <-c.closed:
return
}
}
}
func (c *conn) write(responses chan *Fcall) {
for {
select {
case resp := <-responses:
// TODO(stevvooe): Correctly protect againt overflowing msize from
// handler. This can be done above, in the main message handler
// loop, by adjusting incoming Tread calls to have a Count that
// won't overflow the msize.
if err := c.ch.WriteFcall(c.ctx, resp); err != nil {
if err, ok := err.(net.Error); ok {
if err.Timeout() || err.Temporary() {
// TODO(stevvooe): A full idle timeout on the
// connection should be enforced here. We log here,
// since this is less common.
log.Printf("9p server: temporary error writing fcall: %v", err)
continue
}
}
c.CloseWithError(fmt.Errorf("error writing fcall: %v", err))
return
}
case <-c.ctx.Done():
c.CloseWithError(c.ctx.Err())
return
case <-c.closed:
return
}
}
}
func (c *conn) Close() error {
return c.CloseWithError(nil)
}
func (c *conn) CloseWithError(err error) error {
c.once.Do(func() {
if err == nil {
err = ErrClosed
}
c.err = err
close(c.closed)
})
return c.err
}

View File

@ -1,42 +0,0 @@
package p9p
import "context"
// Session provides the central abstraction for a 9p connection. Clients
// implement sessions and servers serve sessions. Sessions can be proxied by
// serving up a client session.
//
// The interface is also wired up with full context support to manage timeouts
// and resource clean up.
//
// Session represents the operations covered in section 5 of the plan 9 manual
// (http://man.cat-v.org/plan_9/5/). Requests are managed internally, so the
// Flush method is handled by the internal implementation. Consider preceeding
// these all with context to control request timeout.
type Session interface {
Auth(ctx context.Context, afid Fid, uname, aname string) (Qid, error)
Attach(ctx context.Context, fid, afid Fid, uname, aname string) (Qid, error)
Clunk(ctx context.Context, fid Fid) error
Remove(ctx context.Context, fid Fid) error
Walk(ctx context.Context, fid Fid, newfid Fid, names ...string) ([]Qid, error)
// Read follows the semantics of io.ReaderAt.ReadAtt method except it takes
// a contxt and Fid.
Read(ctx context.Context, fid Fid, p []byte, offset int64) (n int, err error)
// Write follows the semantics of io.WriterAt.WriteAt except takes a context and an Fid.
//
// If n == len(p), no error is returned.
// If n < len(p), io.ErrShortWrite will be returned.
Write(ctx context.Context, fid Fid, p []byte, offset int64) (n int, err error)
Open(ctx context.Context, fid Fid, mode Flag) (Qid, uint32, error)
Create(ctx context.Context, parent Fid, name string, perm uint32, mode Flag) (Qid, uint32, error)
Stat(ctx context.Context, fid Fid) (Dir, error)
WStat(ctx context.Context, fid Fid, dir Dir) error
// Version returns the supported version and msize of the session. This
// can be affected by negotiating or the level of support provided by the
// session implementation.
Version() (msize int, version string)
}

View File

@ -1,250 +0,0 @@
package p9p
import (
"errors"
"fmt"
"log"
"net"
"sync"
"context"
)
// roundTripper manages the request and response from the client-side. A
// roundTripper must abide by similar rules to the http.RoundTripper.
// Typically, the roundTripper will manage tag assignment and message
// serialization.
type roundTripper interface {
send(ctx context.Context, msg Message) (Message, error)
}
// transport plays the role of being a client channel manager. It multiplexes
// function calls onto the wire and dispatches responses to blocking calls to
// send. On the whole, transport is thread-safe for calling send
type transport struct {
ctx context.Context
ch Channel
requests chan *fcallRequest
shutdown chan struct{}
once sync.Once // protect closure of shutdown
closed chan struct{}
tags uint16
}
var _ roundTripper = &transport{}
func newTransport(ctx context.Context, ch Channel) roundTripper {
t := &transport{
ctx: ctx,
ch: ch,
requests: make(chan *fcallRequest),
shutdown: make(chan struct{}),
closed: make(chan struct{}),
}
go t.handle()
return t
}
// fcallRequest encompasses the request to send a message via fcall.
type fcallRequest struct {
ctx context.Context
message Message
response chan *Fcall
err chan error
}
func newFcallRequest(ctx context.Context, msg Message) *fcallRequest {
return &fcallRequest{
ctx: ctx,
message: msg,
response: make(chan *Fcall, 1),
err: make(chan error, 1),
}
}
func (t *transport) send(ctx context.Context, msg Message) (Message, error) {
req := newFcallRequest(ctx, msg)
// dispatch the request.
select {
case <-t.closed:
return nil, ErrClosed
case <-ctx.Done():
return nil, ctx.Err()
case t.requests <- req:
}
// wait for the response.
select {
case <-t.closed:
return nil, ErrClosed
case <-ctx.Done():
return nil, ctx.Err()
case err := <-req.err:
return nil, err
case resp := <-req.response:
if resp.Type == Rerror {
// pack the error into something useful
respmesg, ok := resp.Message.(MessageRerror)
if !ok {
return nil, fmt.Errorf("invalid error response: %v", resp)
}
return nil, respmesg
}
return resp.Message, nil
}
}
// allocateTag returns a valid tag given a tag pool map. It receives a hint as
// to where to start the tag search. It returns an error if the allocation is
// not possible. The provided map must not contain NOTAG as a key.
func allocateTag(r *fcallRequest, m map[Tag]*fcallRequest, hint Tag) (Tag, error) {
// The tag pool is depleted if 65535 (0xFFFF) tags are taken.
if len(m) >= 0xFFFF {
return 0, errors.New("tag pool depleted")
}
// Look for the first tag that doesn't exist in the map and return it.
for i := 0; i < 0xFFFF; i++ {
hint++
if hint == NOTAG {
hint = 0
}
if _, exists := m[hint]; !exists {
return hint, nil
}
}
return 0, errors.New("allocateTag: unexpected error")
}
// handle takes messages off the wire and wakes up the waiting tag call.
func (t *transport) handle() {
defer func() {
log.Println("exited handle loop")
close(t.closed)
}()
// the following variable block are protected components owned by this thread.
var (
responses = make(chan *Fcall)
// outstanding provides a map of tags to outstanding requests.
outstanding = map[Tag]*fcallRequest{}
selected Tag
)
// loop to read messages off of the connection
go func() {
defer func() {
log.Println("exited read loop")
t.close() // single main loop
}()
loop:
for {
fcall := new(Fcall)
if err := t.ch.ReadFcall(t.ctx, fcall); err != nil {
switch err := err.(type) {
case net.Error:
if err.Timeout() || err.Temporary() {
// BUG(stevvooe): There may be partial reads under
// timeout errors where this is actually fatal.
// can only retry if we haven't offset the frame.
continue loop
}
}
log.Println("fatal error reading msg:", err)
return
}
select {
case <-t.ctx.Done():
return
case <-t.closed:
log.Println("transport closed")
return
case responses <- fcall:
}
}
}()
for {
select {
case req := <-t.requests:
var err error
selected, err = allocateTag(req, outstanding, selected)
if err != nil {
req.err <- err
continue
}
outstanding[selected] = req
fcall := newFcall(selected, req.message)
// TODO(stevvooe): Consider the case of requests that never
// receive a response. We need to remove the fcall context from
// the tag map and dealloc the tag. We may also want to send a
// flush for the tag.
if err := t.ch.WriteFcall(req.ctx, fcall); err != nil {
delete(outstanding, fcall.Tag)
req.err <- err
}
case b := <-responses:
req, ok := outstanding[b.Tag]
if !ok {
// BUG(stevvooe): The exact handling of an unknown tag is
// unclear at this point. These may not necessarily fatal to
// the session, since they could be messages that the client no
// longer cares for. When we figure this out, replace this
// panic with something more sensible.
panic(fmt.Sprintf("unknown tag received: %v", b))
}
// BUG(stevvooe): Must detect duplicate tag and ensure that we are
// waking up the right caller. If a duplicate is received, the
// entry should not be deleted.
delete(outstanding, b.Tag)
req.response <- b
// TODO(stevvooe): Reclaim tag id.
case <-t.shutdown:
return
case <-t.ctx.Done():
return
}
}
}
func (t *transport) flush(ctx context.Context, tag Tag) error {
// TODO(stevvooe): We need to fire and forget flush messages when a call
// context gets cancelled.
panic("not implemented")
}
func (t *transport) Close() error {
t.close()
select {
case <-t.closed:
return nil
case <-t.ctx.Done():
return t.ctx.Err()
}
}
// close starts the shutdown process.
func (t *transport) close() {
t.once.Do(func() {
close(t.shutdown)
})
}

View File

@ -1,149 +0,0 @@
package p9p
import (
"fmt"
"time"
)
const (
// DefaultMSize messages size used to establish a session.
DefaultMSize = 64 << 10
// DefaultVersion for this package. Currently, the only supported version.
DefaultVersion = "9P2000"
)
// Mode constants for use Dir.Mode.
const (
DMDIR = 0x80000000 // mode bit for directories
DMAPPEND = 0x40000000 // mode bit for append only files
DMEXCL = 0x20000000 // mode bit for exclusive use files
DMMOUNT = 0x10000000 // mode bit for mounted channel
DMAUTH = 0x08000000 // mode bit for authentication file
DMTMP = 0x04000000 // mode bit for non-backed-up files
// 9p2000.u extensions
DMSYMLINK = 0x02000000
DMDEVICE = 0x00800000
DMNAMEDPIPE = 0x00200000
DMSOCKET = 0x00100000
DMSETUID = 0x00080000
DMSETGID = 0x00040000
DMREAD = 0x4 // mode bit for read permission
DMWRITE = 0x2 // mode bit for write permission
DMEXEC = 0x1 // mode bit for execute permission
)
// Flag defines the flag type for use with open and create
type Flag uint8
// Constants to use when opening files.
const (
OREAD Flag = 0x00 // open for read
OWRITE Flag = 0x01 // write
ORDWR Flag = 0x02 // read and write
OEXEC Flag = 0x03 // execute, == read but check execute permission
// PROPOSAL(stevvooe): Possible protocal extension to allow the create of
// symlinks. Initially, the link is created with no value. Read and write
// to read and set the link value.
OSYMLINK Flag = 0x04
OTRUNC Flag = 0x10 // or'ed in (except for exec), truncate file first
OCEXEC Flag = 0x20 // or'ed in, close on exec
ORCLOSE Flag = 0x40 // or'ed in, remove on close
)
// QType indicates the type of a resource within the Qid.
type QType uint8
// Constants for use in Qid to indicate resource type.
const (
QTDIR QType = 0x80 // type bit for directories
QTAPPEND QType = 0x40 // type bit for append only files
QTEXCL QType = 0x20 // type bit for exclusive use files
QTMOUNT QType = 0x10 // type bit for mounted channel
QTAUTH QType = 0x08 // type bit for authentication file
QTTMP QType = 0x04 // type bit for not-backed-up file
QTFILE QType = 0x00 // plain file
)
func (qt QType) String() string {
switch qt {
case QTDIR:
return "dir"
case QTAPPEND:
return "append"
case QTEXCL:
return "excl"
case QTMOUNT:
return "mount"
case QTAUTH:
return "auth"
case QTTMP:
return "tmp"
case QTFILE:
return "file"
}
return "unknown"
}
// Tag uniquely identifies an outstanding fcall in a 9p session.
type Tag uint16
// NOTAG is a reserved values for messages sent before establishing a session,
// such as Tversion.
const NOTAG Tag = ^Tag(0)
// Fid defines a type to hold Fid values.
type Fid uint32
// NOFID indicates the lack of an Fid.
const NOFID Fid = ^Fid(0)
// Qid indicates the type, path and version of the resource returned by a
// server. It is only valid for a session.
//
// Typically, a client maintains a mapping of Fid-Qid as Qids are returned by
// the server.
type Qid struct {
Type QType `9p:"type,1"`
Version uint32
Path uint64
}
func (qid Qid) String() string {
return fmt.Sprintf("qid(%v, v=%x, p=%x)",
qid.Type, qid.Version, qid.Path)
}
// Dir defines the structure used for expressing resources in stat/wstat and
// when reading directories.
type Dir struct {
Type uint16
Dev uint32
Qid Qid
Mode uint32
// BUG(stevvooe): The Year 2038 is coming soon. 9p wire protocol has these
// as 4 byte epoch times. Some possibilities include time dilation fields
// or atemporal files. We can also just not use them and set them to zero.
AccessTime time.Time
ModTime time.Time
Length uint64
Name string
UID string
GID string
MUID string
}
func (d Dir) String() string {
return fmt.Sprintf("dir(%v mode=%v atime=%v mtime=%v length=%v name=%v uid=%v gid=%v muid=%v)",
d.Qid, d.Mode, d.AccessTime, d.ModTime, d.Length, d.Name, d.UID, d.GID, d.MUID)
}

View File

@ -1,112 +0,0 @@
package p9p
import (
"fmt"
"context"
)
// NOTE(stevvooe): This file contains functions for negotiating version on the
// client and server. There are some nasty details to get right for
// downgrading the connection on the server-side that are not present yet.
// Really, these should be refactored into some sort of channel type that can
// support resets through version messages during the protocol exchange.
// clientnegotiate negiotiates the protocol version using channel, blocking
// until a response is received. The received value will be the version
// implemented by the server.
func clientnegotiate(ctx context.Context, ch Channel, version string) (string, error) {
req := newFcall(NOTAG, MessageTversion{
MSize: uint32(ch.MSize()),
Version: version,
})
if err := ch.WriteFcall(ctx, req); err != nil {
return "", err
}
resp := new(Fcall)
if err := ch.ReadFcall(ctx, resp); err != nil {
return "", err
}
switch v := resp.Message.(type) {
case MessageRversion:
if v.Version != version {
// TODO(stevvooe): A stubborn client indeed!
return "", fmt.Errorf("unsupported server version: %v", version)
}
if int(v.MSize) < ch.MSize() {
// upgrade msize if server differs.
ch.SetMSize(int(v.MSize))
}
return v.Version, nil
case error:
return "", v
default:
return "", ErrUnexpectedMsg
}
}
// servernegotiate blocks until a version message is received or a timeout
// occurs. The msize for the tranport will be set from the negotiation. If
// negotiate returns nil, a server may proceed with the connection.
//
// In the future, it might be better to handle the version messages in a
// separate object that manages the session. Each set of version requests
// effectively "reset" a connection, meaning all fids get clunked and all
// outstanding IO is aborted. This is probably slightly racy, in practice with
// a misbehaved client. The main issue is that we cannot tell which session
// messages belong to.
func servernegotiate(ctx context.Context, ch Channel, version string) error {
// wait for the version message over the transport.
req := new(Fcall)
if err := ch.ReadFcall(ctx, req); err != nil {
return err
}
mv, ok := req.Message.(MessageTversion)
if !ok {
return fmt.Errorf("expected version message: %v", mv)
}
respmsg := MessageRversion{
Version: version,
}
if mv.Version != version {
// TODO(stevvooe): Not the best place to do version handling. We need
// to have a way to pass supported versions into this method then have
// it return the actual version. For now, respond with 9P2000 for
// anything that doesn't match the provided version string.
//
// version(9) says "The server may respond with the clients
// version string, or a version string identifying an earlier
// defined protocol version. Currently, the only defined
// version is the 6 characters 9P2000." Therefore, it is always
// OK to respond with this.
respmsg.Version = "9P2000"
}
if int(mv.MSize) < ch.MSize() {
// if the server msize is too large, use the client's suggested msize.
ch.SetMSize(int(mv.MSize))
respmsg.MSize = mv.MSize
} else {
respmsg.MSize = uint32(ch.MSize())
}
resp := newFcall(NOTAG, respmsg)
if err := ch.WriteFcall(ctx, resp); err != nil {
return err
}
if respmsg.Version == "unknown" {
return fmt.Errorf("bad version negotiation")
}
return nil
}

View File

@ -1,139 +0,0 @@
// Go support for Protocol Buffers - Google's data interchange format
//
// Copyright 2016 The Go Authors. All rights reserved.
// https://github.com/golang/protobuf
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package ptypes
// This file implements functions to marshal proto.Message to/from
// google.protobuf.Any message.
import (
"fmt"
"reflect"
"strings"
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes/any"
)
const googleApis = "type.googleapis.com/"
// AnyMessageName returns the name of the message contained in a google.protobuf.Any message.
//
// Note that regular type assertions should be done using the Is
// function. AnyMessageName is provided for less common use cases like filtering a
// sequence of Any messages based on a set of allowed message type names.
func AnyMessageName(any *any.Any) (string, error) {
if any == nil {
return "", fmt.Errorf("message is nil")
}
slash := strings.LastIndex(any.TypeUrl, "/")
if slash < 0 {
return "", fmt.Errorf("message type url %q is invalid", any.TypeUrl)
}
return any.TypeUrl[slash+1:], nil
}
// MarshalAny takes the protocol buffer and encodes it into google.protobuf.Any.
func MarshalAny(pb proto.Message) (*any.Any, error) {
value, err := proto.Marshal(pb)
if err != nil {
return nil, err
}
return &any.Any{TypeUrl: googleApis + proto.MessageName(pb), Value: value}, nil
}
// DynamicAny is a value that can be passed to UnmarshalAny to automatically
// allocate a proto.Message for the type specified in a google.protobuf.Any
// message. The allocated message is stored in the embedded proto.Message.
//
// Example:
//
// var x ptypes.DynamicAny
// if err := ptypes.UnmarshalAny(a, &x); err != nil { ... }
// fmt.Printf("unmarshaled message: %v", x.Message)
type DynamicAny struct {
proto.Message
}
// Empty returns a new proto.Message of the type specified in a
// google.protobuf.Any message. It returns an error if corresponding message
// type isn't linked in.
func Empty(any *any.Any) (proto.Message, error) {
aname, err := AnyMessageName(any)
if err != nil {
return nil, err
}
t := proto.MessageType(aname)
if t == nil {
return nil, fmt.Errorf("any: message type %q isn't linked in", aname)
}
return reflect.New(t.Elem()).Interface().(proto.Message), nil
}
// UnmarshalAny parses the protocol buffer representation in a google.protobuf.Any
// message and places the decoded result in pb. It returns an error if type of
// contents of Any message does not match type of pb message.
//
// pb can be a proto.Message, or a *DynamicAny.
func UnmarshalAny(any *any.Any, pb proto.Message) error {
if d, ok := pb.(*DynamicAny); ok {
if d.Message == nil {
var err error
d.Message, err = Empty(any)
if err != nil {
return err
}
}
return UnmarshalAny(any, d.Message)
}
aname, err := AnyMessageName(any)
if err != nil {
return err
}
mname := proto.MessageName(pb)
if aname != mname {
return fmt.Errorf("mismatched message type: got %q want %q", aname, mname)
}
return proto.Unmarshal(any.Value, pb)
}
// Is returns true if any value contains a given message type.
func Is(any *any.Any, pb proto.Message) bool {
aname, err := AnyMessageName(any)
if err != nil {
return false
}
return aname == proto.MessageName(pb)
}

View File

@ -1,191 +0,0 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: google/protobuf/any.proto
package any // import "github.com/golang/protobuf/ptypes/any"
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
// `Any` contains an arbitrary serialized protocol buffer message along with a
// URL that describes the type of the serialized message.
//
// Protobuf library provides support to pack/unpack Any values in the form
// of utility functions or additional generated methods of the Any type.
//
// Example 1: Pack and unpack a message in C++.
//
// Foo foo = ...;
// Any any;
// any.PackFrom(foo);
// ...
// if (any.UnpackTo(&foo)) {
// ...
// }
//
// Example 2: Pack and unpack a message in Java.
//
// Foo foo = ...;
// Any any = Any.pack(foo);
// ...
// if (any.is(Foo.class)) {
// foo = any.unpack(Foo.class);
// }
//
// Example 3: Pack and unpack a message in Python.
//
// foo = Foo(...)
// any = Any()
// any.Pack(foo)
// ...
// if any.Is(Foo.DESCRIPTOR):
// any.Unpack(foo)
// ...
//
// Example 4: Pack and unpack a message in Go
//
// foo := &pb.Foo{...}
// any, err := ptypes.MarshalAny(foo)
// ...
// foo := &pb.Foo{}
// if err := ptypes.UnmarshalAny(any, foo); err != nil {
// ...
// }
//
// The pack methods provided by protobuf library will by default use
// 'type.googleapis.com/full.type.name' as the type URL and the unpack
// methods only use the fully qualified type name after the last '/'
// in the type URL, for example "foo.bar.com/x/y.z" will yield type
// name "y.z".
//
//
// JSON
// ====
// The JSON representation of an `Any` value uses the regular
// representation of the deserialized, embedded message, with an
// additional field `@type` which contains the type URL. Example:
//
// package google.profile;
// message Person {
// string first_name = 1;
// string last_name = 2;
// }
//
// {
// "@type": "type.googleapis.com/google.profile.Person",
// "firstName": <string>,
// "lastName": <string>
// }
//
// If the embedded message type is well-known and has a custom JSON
// representation, that representation will be embedded adding a field
// `value` which holds the custom JSON in addition to the `@type`
// field. Example (for message [google.protobuf.Duration][]):
//
// {
// "@type": "type.googleapis.com/google.protobuf.Duration",
// "value": "1.212s"
// }
//
type Any struct {
// A URL/resource name whose content describes the type of the
// serialized protocol buffer message.
//
// For URLs which use the scheme `http`, `https`, or no scheme, the
// following restrictions and interpretations apply:
//
// * If no scheme is provided, `https` is assumed.
// * The last segment of the URL's path must represent the fully
// qualified name of the type (as in `path/google.protobuf.Duration`).
// The name should be in a canonical form (e.g., leading "." is
// not accepted).
// * An HTTP GET on the URL must yield a [google.protobuf.Type][]
// value in binary format, or produce an error.
// * Applications are allowed to cache lookup results based on the
// URL, or have them precompiled into a binary to avoid any
// lookup. Therefore, binary compatibility needs to be preserved
// on changes to types. (Use versioned type names to manage
// breaking changes.)
//
// Schemes other than `http`, `https` (or the empty scheme) might be
// used with implementation specific semantics.
//
TypeUrl string `protobuf:"bytes,1,opt,name=type_url,json=typeUrl" json:"type_url,omitempty"`
// Must be a valid serialized protocol buffer of the above specified type.
Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Any) Reset() { *m = Any{} }
func (m *Any) String() string { return proto.CompactTextString(m) }
func (*Any) ProtoMessage() {}
func (*Any) Descriptor() ([]byte, []int) {
return fileDescriptor_any_744b9ca530f228db, []int{0}
}
func (*Any) XXX_WellKnownType() string { return "Any" }
func (m *Any) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Any.Unmarshal(m, b)
}
func (m *Any) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Any.Marshal(b, m, deterministic)
}
func (dst *Any) XXX_Merge(src proto.Message) {
xxx_messageInfo_Any.Merge(dst, src)
}
func (m *Any) XXX_Size() int {
return xxx_messageInfo_Any.Size(m)
}
func (m *Any) XXX_DiscardUnknown() {
xxx_messageInfo_Any.DiscardUnknown(m)
}
var xxx_messageInfo_Any proto.InternalMessageInfo
func (m *Any) GetTypeUrl() string {
if m != nil {
return m.TypeUrl
}
return ""
}
func (m *Any) GetValue() []byte {
if m != nil {
return m.Value
}
return nil
}
func init() {
proto.RegisterType((*Any)(nil), "google.protobuf.Any")
}
func init() { proto.RegisterFile("google/protobuf/any.proto", fileDescriptor_any_744b9ca530f228db) }
var fileDescriptor_any_744b9ca530f228db = []byte{
// 185 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x4c, 0xcf, 0xcf, 0x4f,
0xcf, 0x49, 0xd5, 0x2f, 0x28, 0xca, 0x2f, 0xc9, 0x4f, 0x2a, 0x4d, 0xd3, 0x4f, 0xcc, 0xab, 0xd4,
0x03, 0x73, 0x84, 0xf8, 0x21, 0x52, 0x7a, 0x30, 0x29, 0x25, 0x33, 0x2e, 0x66, 0xc7, 0xbc, 0x4a,
0x21, 0x49, 0x2e, 0x8e, 0x92, 0xca, 0x82, 0xd4, 0xf8, 0xd2, 0xa2, 0x1c, 0x09, 0x46, 0x05, 0x46,
0x0d, 0xce, 0x20, 0x76, 0x10, 0x3f, 0xb4, 0x28, 0x47, 0x48, 0x84, 0x8b, 0xb5, 0x2c, 0x31, 0xa7,
0x34, 0x55, 0x82, 0x49, 0x81, 0x51, 0x83, 0x27, 0x08, 0xc2, 0x71, 0xca, 0xe7, 0x12, 0x4e, 0xce,
0xcf, 0xd5, 0x43, 0x33, 0xce, 0x89, 0xc3, 0x31, 0xaf, 0x32, 0x00, 0xc4, 0x09, 0x60, 0x8c, 0x52,
0x4d, 0xcf, 0x2c, 0xc9, 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x4f, 0xcf, 0xcf, 0x49, 0xcc,
0x4b, 0x47, 0xb8, 0xa8, 0x00, 0x64, 0x7a, 0x31, 0xc8, 0x61, 0x8b, 0x98, 0x98, 0xdd, 0x03, 0x9c,
0x56, 0x31, 0xc9, 0xb9, 0x43, 0x8c, 0x0a, 0x80, 0x2a, 0xd1, 0x0b, 0x4f, 0xcd, 0xc9, 0xf1, 0xce,
0xcb, 0x2f, 0xcf, 0x0b, 0x01, 0x29, 0x4d, 0x62, 0x03, 0xeb, 0x35, 0x06, 0x04, 0x00, 0x00, 0xff,
0xff, 0x13, 0xf8, 0xe8, 0x42, 0xdd, 0x00, 0x00, 0x00,
}

View File

@ -1,149 +0,0 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
syntax = "proto3";
package google.protobuf;
option csharp_namespace = "Google.Protobuf.WellKnownTypes";
option go_package = "github.com/golang/protobuf/ptypes/any";
option java_package = "com.google.protobuf";
option java_outer_classname = "AnyProto";
option java_multiple_files = true;
option objc_class_prefix = "GPB";
// `Any` contains an arbitrary serialized protocol buffer message along with a
// URL that describes the type of the serialized message.
//
// Protobuf library provides support to pack/unpack Any values in the form
// of utility functions or additional generated methods of the Any type.
//
// Example 1: Pack and unpack a message in C++.
//
// Foo foo = ...;
// Any any;
// any.PackFrom(foo);
// ...
// if (any.UnpackTo(&foo)) {
// ...
// }
//
// Example 2: Pack and unpack a message in Java.
//
// Foo foo = ...;
// Any any = Any.pack(foo);
// ...
// if (any.is(Foo.class)) {
// foo = any.unpack(Foo.class);
// }
//
// Example 3: Pack and unpack a message in Python.
//
// foo = Foo(...)
// any = Any()
// any.Pack(foo)
// ...
// if any.Is(Foo.DESCRIPTOR):
// any.Unpack(foo)
// ...
//
// Example 4: Pack and unpack a message in Go
//
// foo := &pb.Foo{...}
// any, err := ptypes.MarshalAny(foo)
// ...
// foo := &pb.Foo{}
// if err := ptypes.UnmarshalAny(any, foo); err != nil {
// ...
// }
//
// The pack methods provided by protobuf library will by default use
// 'type.googleapis.com/full.type.name' as the type URL and the unpack
// methods only use the fully qualified type name after the last '/'
// in the type URL, for example "foo.bar.com/x/y.z" will yield type
// name "y.z".
//
//
// JSON
// ====
// The JSON representation of an `Any` value uses the regular
// representation of the deserialized, embedded message, with an
// additional field `@type` which contains the type URL. Example:
//
// package google.profile;
// message Person {
// string first_name = 1;
// string last_name = 2;
// }
//
// {
// "@type": "type.googleapis.com/google.profile.Person",
// "firstName": <string>,
// "lastName": <string>
// }
//
// If the embedded message type is well-known and has a custom JSON
// representation, that representation will be embedded adding a field
// `value` which holds the custom JSON in addition to the `@type`
// field. Example (for message [google.protobuf.Duration][]):
//
// {
// "@type": "type.googleapis.com/google.protobuf.Duration",
// "value": "1.212s"
// }
//
message Any {
// A URL/resource name whose content describes the type of the
// serialized protocol buffer message.
//
// For URLs which use the scheme `http`, `https`, or no scheme, the
// following restrictions and interpretations apply:
//
// * If no scheme is provided, `https` is assumed.
// * The last segment of the URL's path must represent the fully
// qualified name of the type (as in `path/google.protobuf.Duration`).
// The name should be in a canonical form (e.g., leading "." is
// not accepted).
// * An HTTP GET on the URL must yield a [google.protobuf.Type][]
// value in binary format, or produce an error.
// * Applications are allowed to cache lookup results based on the
// URL, or have them precompiled into a binary to avoid any
// lookup. Therefore, binary compatibility needs to be preserved
// on changes to types. (Use versioned type names to manage
// breaking changes.)
//
// Schemes other than `http`, `https` (or the empty scheme) might be
// used with implementation specific semantics.
//
string type_url = 1;
// Must be a valid serialized protocol buffer of the above specified type.
bytes value = 2;
}

View File

@ -1,35 +0,0 @@
// Go support for Protocol Buffers - Google's data interchange format
//
// Copyright 2016 The Go Authors. All rights reserved.
// https://github.com/golang/protobuf
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
/*
Package ptypes contains code for interacting with well-known types.
*/
package ptypes

View File

@ -1,102 +0,0 @@
// Go support for Protocol Buffers - Google's data interchange format
//
// Copyright 2016 The Go Authors. All rights reserved.
// https://github.com/golang/protobuf
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package ptypes
// This file implements conversions between google.protobuf.Duration
// and time.Duration.
import (
"errors"
"fmt"
"time"
durpb "github.com/golang/protobuf/ptypes/duration"
)
const (
// Range of a durpb.Duration in seconds, as specified in
// google/protobuf/duration.proto. This is about 10,000 years in seconds.
maxSeconds = int64(10000 * 365.25 * 24 * 60 * 60)
minSeconds = -maxSeconds
)
// validateDuration determines whether the durpb.Duration is valid according to the
// definition in google/protobuf/duration.proto. A valid durpb.Duration
// may still be too large to fit into a time.Duration (the range of durpb.Duration
// is about 10,000 years, and the range of time.Duration is about 290).
func validateDuration(d *durpb.Duration) error {
if d == nil {
return errors.New("duration: nil Duration")
}
if d.Seconds < minSeconds || d.Seconds > maxSeconds {
return fmt.Errorf("duration: %v: seconds out of range", d)
}
if d.Nanos <= -1e9 || d.Nanos >= 1e9 {
return fmt.Errorf("duration: %v: nanos out of range", d)
}
// Seconds and Nanos must have the same sign, unless d.Nanos is zero.
if (d.Seconds < 0 && d.Nanos > 0) || (d.Seconds > 0 && d.Nanos < 0) {
return fmt.Errorf("duration: %v: seconds and nanos have different signs", d)
}
return nil
}
// Duration converts a durpb.Duration to a time.Duration. Duration
// returns an error if the durpb.Duration is invalid or is too large to be
// represented in a time.Duration.
func Duration(p *durpb.Duration) (time.Duration, error) {
if err := validateDuration(p); err != nil {
return 0, err
}
d := time.Duration(p.Seconds) * time.Second
if int64(d/time.Second) != p.Seconds {
return 0, fmt.Errorf("duration: %v is out of range for time.Duration", p)
}
if p.Nanos != 0 {
d += time.Duration(p.Nanos)
if (d < 0) != (p.Nanos < 0) {
return 0, fmt.Errorf("duration: %v is out of range for time.Duration", p)
}
}
return d, nil
}
// DurationProto converts a time.Duration to a durpb.Duration.
func DurationProto(d time.Duration) *durpb.Duration {
nanos := d.Nanoseconds()
secs := nanos / 1e9
nanos -= secs * 1e9
return &durpb.Duration{
Seconds: secs,
Nanos: int32(nanos),
}
}

View File

@ -1,159 +0,0 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: google/protobuf/duration.proto
package duration // import "github.com/golang/protobuf/ptypes/duration"
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
// A Duration represents a signed, fixed-length span of time represented
// as a count of seconds and fractions of seconds at nanosecond
// resolution. It is independent of any calendar and concepts like "day"
// or "month". It is related to Timestamp in that the difference between
// two Timestamp values is a Duration and it can be added or subtracted
// from a Timestamp. Range is approximately +-10,000 years.
//
// # Examples
//
// Example 1: Compute Duration from two Timestamps in pseudo code.
//
// Timestamp start = ...;
// Timestamp end = ...;
// Duration duration = ...;
//
// duration.seconds = end.seconds - start.seconds;
// duration.nanos = end.nanos - start.nanos;
//
// if (duration.seconds < 0 && duration.nanos > 0) {
// duration.seconds += 1;
// duration.nanos -= 1000000000;
// } else if (durations.seconds > 0 && duration.nanos < 0) {
// duration.seconds -= 1;
// duration.nanos += 1000000000;
// }
//
// Example 2: Compute Timestamp from Timestamp + Duration in pseudo code.
//
// Timestamp start = ...;
// Duration duration = ...;
// Timestamp end = ...;
//
// end.seconds = start.seconds + duration.seconds;
// end.nanos = start.nanos + duration.nanos;
//
// if (end.nanos < 0) {
// end.seconds -= 1;
// end.nanos += 1000000000;
// } else if (end.nanos >= 1000000000) {
// end.seconds += 1;
// end.nanos -= 1000000000;
// }
//
// Example 3: Compute Duration from datetime.timedelta in Python.
//
// td = datetime.timedelta(days=3, minutes=10)
// duration = Duration()
// duration.FromTimedelta(td)
//
// # JSON Mapping
//
// In JSON format, the Duration type is encoded as a string rather than an
// object, where the string ends in the suffix "s" (indicating seconds) and
// is preceded by the number of seconds, with nanoseconds expressed as
// fractional seconds. For example, 3 seconds with 0 nanoseconds should be
// encoded in JSON format as "3s", while 3 seconds and 1 nanosecond should
// be expressed in JSON format as "3.000000001s", and 3 seconds and 1
// microsecond should be expressed in JSON format as "3.000001s".
//
//
type Duration struct {
// Signed seconds of the span of time. Must be from -315,576,000,000
// to +315,576,000,000 inclusive. Note: these bounds are computed from:
// 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years
Seconds int64 `protobuf:"varint,1,opt,name=seconds" json:"seconds,omitempty"`
// Signed fractions of a second at nanosecond resolution of the span
// of time. Durations less than one second are represented with a 0
// `seconds` field and a positive or negative `nanos` field. For durations
// of one second or more, a non-zero value for the `nanos` field must be
// of the same sign as the `seconds` field. Must be from -999,999,999
// to +999,999,999 inclusive.
Nanos int32 `protobuf:"varint,2,opt,name=nanos" json:"nanos,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Duration) Reset() { *m = Duration{} }
func (m *Duration) String() string { return proto.CompactTextString(m) }
func (*Duration) ProtoMessage() {}
func (*Duration) Descriptor() ([]byte, []int) {
return fileDescriptor_duration_e7d612259e3f0613, []int{0}
}
func (*Duration) XXX_WellKnownType() string { return "Duration" }
func (m *Duration) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Duration.Unmarshal(m, b)
}
func (m *Duration) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Duration.Marshal(b, m, deterministic)
}
func (dst *Duration) XXX_Merge(src proto.Message) {
xxx_messageInfo_Duration.Merge(dst, src)
}
func (m *Duration) XXX_Size() int {
return xxx_messageInfo_Duration.Size(m)
}
func (m *Duration) XXX_DiscardUnknown() {
xxx_messageInfo_Duration.DiscardUnknown(m)
}
var xxx_messageInfo_Duration proto.InternalMessageInfo
func (m *Duration) GetSeconds() int64 {
if m != nil {
return m.Seconds
}
return 0
}
func (m *Duration) GetNanos() int32 {
if m != nil {
return m.Nanos
}
return 0
}
func init() {
proto.RegisterType((*Duration)(nil), "google.protobuf.Duration")
}
func init() {
proto.RegisterFile("google/protobuf/duration.proto", fileDescriptor_duration_e7d612259e3f0613)
}
var fileDescriptor_duration_e7d612259e3f0613 = []byte{
// 190 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x4b, 0xcf, 0xcf, 0x4f,
0xcf, 0x49, 0xd5, 0x2f, 0x28, 0xca, 0x2f, 0xc9, 0x4f, 0x2a, 0x4d, 0xd3, 0x4f, 0x29, 0x2d, 0x4a,
0x2c, 0xc9, 0xcc, 0xcf, 0xd3, 0x03, 0x8b, 0x08, 0xf1, 0x43, 0xe4, 0xf5, 0x60, 0xf2, 0x4a, 0x56,
0x5c, 0x1c, 0x2e, 0x50, 0x25, 0x42, 0x12, 0x5c, 0xec, 0xc5, 0xa9, 0xc9, 0xf9, 0x79, 0x29, 0xc5,
0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0xcc, 0x41, 0x30, 0xae, 0x90, 0x08, 0x17, 0x6b, 0x5e, 0x62, 0x5e,
0x7e, 0xb1, 0x04, 0x93, 0x02, 0xa3, 0x06, 0x6b, 0x10, 0x84, 0xe3, 0x54, 0xc3, 0x25, 0x9c, 0x9c,
0x9f, 0xab, 0x87, 0x66, 0xa4, 0x13, 0x2f, 0xcc, 0xc0, 0x00, 0x90, 0x48, 0x00, 0x63, 0x94, 0x56,
0x7a, 0x66, 0x49, 0x46, 0x69, 0x92, 0x5e, 0x72, 0x7e, 0xae, 0x7e, 0x7a, 0x7e, 0x4e, 0x62, 0x5e,
0x3a, 0xc2, 0x7d, 0x05, 0x25, 0x95, 0x05, 0xa9, 0xc5, 0x70, 0x67, 0xfe, 0x60, 0x64, 0x5c, 0xc4,
0xc4, 0xec, 0x1e, 0xe0, 0xb4, 0x8a, 0x49, 0xce, 0x1d, 0x62, 0x6e, 0x00, 0x54, 0xa9, 0x5e, 0x78,
0x6a, 0x4e, 0x8e, 0x77, 0x5e, 0x7e, 0x79, 0x5e, 0x08, 0x48, 0x4b, 0x12, 0x1b, 0xd8, 0x0c, 0x63,
0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0xdc, 0x84, 0x30, 0xff, 0xf3, 0x00, 0x00, 0x00,
}

View File

@ -1,117 +0,0 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
syntax = "proto3";
package google.protobuf;
option csharp_namespace = "Google.Protobuf.WellKnownTypes";
option cc_enable_arenas = true;
option go_package = "github.com/golang/protobuf/ptypes/duration";
option java_package = "com.google.protobuf";
option java_outer_classname = "DurationProto";
option java_multiple_files = true;
option objc_class_prefix = "GPB";
// A Duration represents a signed, fixed-length span of time represented
// as a count of seconds and fractions of seconds at nanosecond
// resolution. It is independent of any calendar and concepts like "day"
// or "month". It is related to Timestamp in that the difference between
// two Timestamp values is a Duration and it can be added or subtracted
// from a Timestamp. Range is approximately +-10,000 years.
//
// # Examples
//
// Example 1: Compute Duration from two Timestamps in pseudo code.
//
// Timestamp start = ...;
// Timestamp end = ...;
// Duration duration = ...;
//
// duration.seconds = end.seconds - start.seconds;
// duration.nanos = end.nanos - start.nanos;
//
// if (duration.seconds < 0 && duration.nanos > 0) {
// duration.seconds += 1;
// duration.nanos -= 1000000000;
// } else if (durations.seconds > 0 && duration.nanos < 0) {
// duration.seconds -= 1;
// duration.nanos += 1000000000;
// }
//
// Example 2: Compute Timestamp from Timestamp + Duration in pseudo code.
//
// Timestamp start = ...;
// Duration duration = ...;
// Timestamp end = ...;
//
// end.seconds = start.seconds + duration.seconds;
// end.nanos = start.nanos + duration.nanos;
//
// if (end.nanos < 0) {
// end.seconds -= 1;
// end.nanos += 1000000000;
// } else if (end.nanos >= 1000000000) {
// end.seconds += 1;
// end.nanos -= 1000000000;
// }
//
// Example 3: Compute Duration from datetime.timedelta in Python.
//
// td = datetime.timedelta(days=3, minutes=10)
// duration = Duration()
// duration.FromTimedelta(td)
//
// # JSON Mapping
//
// In JSON format, the Duration type is encoded as a string rather than an
// object, where the string ends in the suffix "s" (indicating seconds) and
// is preceded by the number of seconds, with nanoseconds expressed as
// fractional seconds. For example, 3 seconds with 0 nanoseconds should be
// encoded in JSON format as "3s", while 3 seconds and 1 nanosecond should
// be expressed in JSON format as "3.000000001s", and 3 seconds and 1
// microsecond should be expressed in JSON format as "3.000001s".
//
//
message Duration {
// Signed seconds of the span of time. Must be from -315,576,000,000
// to +315,576,000,000 inclusive. Note: these bounds are computed from:
// 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years
int64 seconds = 1;
// Signed fractions of a second at nanosecond resolution of the span
// of time. Durations less than one second are represented with a 0
// `seconds` field and a positive or negative `nanos` field. For durations
// of one second or more, a non-zero value for the `nanos` field must be
// of the same sign as the `seconds` field. Must be from -999,999,999
// to +999,999,999 inclusive.
int32 nanos = 2;
}

View File

@ -1,134 +0,0 @@
// Go support for Protocol Buffers - Google's data interchange format
//
// Copyright 2016 The Go Authors. All rights reserved.
// https://github.com/golang/protobuf
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package ptypes
// This file implements operations on google.protobuf.Timestamp.
import (
"errors"
"fmt"
"time"
tspb "github.com/golang/protobuf/ptypes/timestamp"
)
const (
// Seconds field of the earliest valid Timestamp.
// This is time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC).Unix().
minValidSeconds = -62135596800
// Seconds field just after the latest valid Timestamp.
// This is time.Date(10000, 1, 1, 0, 0, 0, 0, time.UTC).Unix().
maxValidSeconds = 253402300800
)
// validateTimestamp determines whether a Timestamp is valid.
// A valid timestamp represents a time in the range
// [0001-01-01, 10000-01-01) and has a Nanos field
// in the range [0, 1e9).
//
// If the Timestamp is valid, validateTimestamp returns nil.
// Otherwise, it returns an error that describes
// the problem.
//
// Every valid Timestamp can be represented by a time.Time, but the converse is not true.
func validateTimestamp(ts *tspb.Timestamp) error {
if ts == nil {
return errors.New("timestamp: nil Timestamp")
}
if ts.Seconds < minValidSeconds {
return fmt.Errorf("timestamp: %v before 0001-01-01", ts)
}
if ts.Seconds >= maxValidSeconds {
return fmt.Errorf("timestamp: %v after 10000-01-01", ts)
}
if ts.Nanos < 0 || ts.Nanos >= 1e9 {
return fmt.Errorf("timestamp: %v: nanos not in range [0, 1e9)", ts)
}
return nil
}
// Timestamp converts a google.protobuf.Timestamp proto to a time.Time.
// It returns an error if the argument is invalid.
//
// Unlike most Go functions, if Timestamp returns an error, the first return value
// is not the zero time.Time. Instead, it is the value obtained from the
// time.Unix function when passed the contents of the Timestamp, in the UTC
// locale. This may or may not be a meaningful time; many invalid Timestamps
// do map to valid time.Times.
//
// A nil Timestamp returns an error. The first return value in that case is
// undefined.
func Timestamp(ts *tspb.Timestamp) (time.Time, error) {
// Don't return the zero value on error, because corresponds to a valid
// timestamp. Instead return whatever time.Unix gives us.
var t time.Time
if ts == nil {
t = time.Unix(0, 0).UTC() // treat nil like the empty Timestamp
} else {
t = time.Unix(ts.Seconds, int64(ts.Nanos)).UTC()
}
return t, validateTimestamp(ts)
}
// TimestampNow returns a google.protobuf.Timestamp for the current time.
func TimestampNow() *tspb.Timestamp {
ts, err := TimestampProto(time.Now())
if err != nil {
panic("ptypes: time.Now() out of Timestamp range")
}
return ts
}
// TimestampProto converts the time.Time to a google.protobuf.Timestamp proto.
// It returns an error if the resulting Timestamp is invalid.
func TimestampProto(t time.Time) (*tspb.Timestamp, error) {
seconds := t.Unix()
nanos := int32(t.Sub(time.Unix(seconds, 0)))
ts := &tspb.Timestamp{
Seconds: seconds,
Nanos: nanos,
}
if err := validateTimestamp(ts); err != nil {
return nil, err
}
return ts, nil
}
// TimestampString returns the RFC 3339 string for valid Timestamps. For invalid
// Timestamps, it returns an error message in parentheses.
func TimestampString(ts *tspb.Timestamp) string {
t, err := Timestamp(ts)
if err != nil {
return fmt.Sprintf("(%v)", err)
}
return t.Format(time.RFC3339Nano)
}

View File

@ -1,175 +0,0 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: google/protobuf/timestamp.proto
package timestamp // import "github.com/golang/protobuf/ptypes/timestamp"
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
// A Timestamp represents a point in time independent of any time zone
// or calendar, represented as seconds and fractions of seconds at
// nanosecond resolution in UTC Epoch time. It is encoded using the
// Proleptic Gregorian Calendar which extends the Gregorian calendar
// backwards to year one. It is encoded assuming all minutes are 60
// seconds long, i.e. leap seconds are "smeared" so that no leap second
// table is needed for interpretation. Range is from
// 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z.
// By restricting to that range, we ensure that we can convert to
// and from RFC 3339 date strings.
// See [https://www.ietf.org/rfc/rfc3339.txt](https://www.ietf.org/rfc/rfc3339.txt).
//
// # Examples
//
// Example 1: Compute Timestamp from POSIX `time()`.
//
// Timestamp timestamp;
// timestamp.set_seconds(time(NULL));
// timestamp.set_nanos(0);
//
// Example 2: Compute Timestamp from POSIX `gettimeofday()`.
//
// struct timeval tv;
// gettimeofday(&tv, NULL);
//
// Timestamp timestamp;
// timestamp.set_seconds(tv.tv_sec);
// timestamp.set_nanos(tv.tv_usec * 1000);
//
// Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`.
//
// FILETIME ft;
// GetSystemTimeAsFileTime(&ft);
// UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
//
// // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z
// // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z.
// Timestamp timestamp;
// timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL));
// timestamp.set_nanos((INT32) ((ticks % 10000000) * 100));
//
// Example 4: Compute Timestamp from Java `System.currentTimeMillis()`.
//
// long millis = System.currentTimeMillis();
//
// Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)
// .setNanos((int) ((millis % 1000) * 1000000)).build();
//
//
// Example 5: Compute Timestamp from current time in Python.
//
// timestamp = Timestamp()
// timestamp.GetCurrentTime()
//
// # JSON Mapping
//
// In JSON format, the Timestamp type is encoded as a string in the
// [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the
// format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z"
// where {year} is always expressed using four digits while {month}, {day},
// {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional
// seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution),
// are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone
// is required, though only UTC (as indicated by "Z") is presently supported.
//
// For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past
// 01:30 UTC on January 15, 2017.
//
// In JavaScript, one can convert a Date object to this format using the
// standard [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString]
// method. In Python, a standard `datetime.datetime` object can be converted
// to this format using [`strftime`](https://docs.python.org/2/library/time.html#time.strftime)
// with the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one
// can use the Joda Time's [`ISODateTimeFormat.dateTime()`](
// http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime--)
// to obtain a formatter capable of generating timestamps in this format.
//
//
type Timestamp struct {
// Represents seconds of UTC time since Unix epoch
// 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
// 9999-12-31T23:59:59Z inclusive.
Seconds int64 `protobuf:"varint,1,opt,name=seconds" json:"seconds,omitempty"`
// Non-negative fractions of a second at nanosecond resolution. Negative
// second values with fractions must still have non-negative nanos values
// that count forward in time. Must be from 0 to 999,999,999
// inclusive.
Nanos int32 `protobuf:"varint,2,opt,name=nanos" json:"nanos,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Timestamp) Reset() { *m = Timestamp{} }
func (m *Timestamp) String() string { return proto.CompactTextString(m) }
func (*Timestamp) ProtoMessage() {}
func (*Timestamp) Descriptor() ([]byte, []int) {
return fileDescriptor_timestamp_b826e8e5fba671a8, []int{0}
}
func (*Timestamp) XXX_WellKnownType() string { return "Timestamp" }
func (m *Timestamp) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Timestamp.Unmarshal(m, b)
}
func (m *Timestamp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Timestamp.Marshal(b, m, deterministic)
}
func (dst *Timestamp) XXX_Merge(src proto.Message) {
xxx_messageInfo_Timestamp.Merge(dst, src)
}
func (m *Timestamp) XXX_Size() int {
return xxx_messageInfo_Timestamp.Size(m)
}
func (m *Timestamp) XXX_DiscardUnknown() {
xxx_messageInfo_Timestamp.DiscardUnknown(m)
}
var xxx_messageInfo_Timestamp proto.InternalMessageInfo
func (m *Timestamp) GetSeconds() int64 {
if m != nil {
return m.Seconds
}
return 0
}
func (m *Timestamp) GetNanos() int32 {
if m != nil {
return m.Nanos
}
return 0
}
func init() {
proto.RegisterType((*Timestamp)(nil), "google.protobuf.Timestamp")
}
func init() {
proto.RegisterFile("google/protobuf/timestamp.proto", fileDescriptor_timestamp_b826e8e5fba671a8)
}
var fileDescriptor_timestamp_b826e8e5fba671a8 = []byte{
// 191 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x4f, 0xcf, 0xcf, 0x4f,
0xcf, 0x49, 0xd5, 0x2f, 0x28, 0xca, 0x2f, 0xc9, 0x4f, 0x2a, 0x4d, 0xd3, 0x2f, 0xc9, 0xcc, 0x4d,
0x2d, 0x2e, 0x49, 0xcc, 0x2d, 0xd0, 0x03, 0x0b, 0x09, 0xf1, 0x43, 0x14, 0xe8, 0xc1, 0x14, 0x28,
0x59, 0x73, 0x71, 0x86, 0xc0, 0xd4, 0x08, 0x49, 0x70, 0xb1, 0x17, 0xa7, 0x26, 0xe7, 0xe7, 0xa5,
0x14, 0x4b, 0x30, 0x2a, 0x30, 0x6a, 0x30, 0x07, 0xc1, 0xb8, 0x42, 0x22, 0x5c, 0xac, 0x79, 0x89,
0x79, 0xf9, 0xc5, 0x12, 0x4c, 0x0a, 0x8c, 0x1a, 0xac, 0x41, 0x10, 0x8e, 0x53, 0x1d, 0x97, 0x70,
0x72, 0x7e, 0xae, 0x1e, 0x9a, 0x99, 0x4e, 0x7c, 0x70, 0x13, 0x03, 0x40, 0x42, 0x01, 0x8c, 0x51,
0xda, 0xe9, 0x99, 0x25, 0x19, 0xa5, 0x49, 0x7a, 0xc9, 0xf9, 0xb9, 0xfa, 0xe9, 0xf9, 0x39, 0x89,
0x79, 0xe9, 0x08, 0x27, 0x16, 0x94, 0x54, 0x16, 0xa4, 0x16, 0x23, 0x5c, 0xfa, 0x83, 0x91, 0x71,
0x11, 0x13, 0xb3, 0x7b, 0x80, 0xd3, 0x2a, 0x26, 0x39, 0x77, 0x88, 0xc9, 0x01, 0x50, 0xb5, 0x7a,
0xe1, 0xa9, 0x39, 0x39, 0xde, 0x79, 0xf9, 0xe5, 0x79, 0x21, 0x20, 0x3d, 0x49, 0x6c, 0x60, 0x43,
0x8c, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0xbc, 0x77, 0x4a, 0x07, 0xf7, 0x00, 0x00, 0x00,
}

View File

@ -1,133 +0,0 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
syntax = "proto3";
package google.protobuf;
option csharp_namespace = "Google.Protobuf.WellKnownTypes";
option cc_enable_arenas = true;
option go_package = "github.com/golang/protobuf/ptypes/timestamp";
option java_package = "com.google.protobuf";
option java_outer_classname = "TimestampProto";
option java_multiple_files = true;
option objc_class_prefix = "GPB";
// A Timestamp represents a point in time independent of any time zone
// or calendar, represented as seconds and fractions of seconds at
// nanosecond resolution in UTC Epoch time. It is encoded using the
// Proleptic Gregorian Calendar which extends the Gregorian calendar
// backwards to year one. It is encoded assuming all minutes are 60
// seconds long, i.e. leap seconds are "smeared" so that no leap second
// table is needed for interpretation. Range is from
// 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z.
// By restricting to that range, we ensure that we can convert to
// and from RFC 3339 date strings.
// See [https://www.ietf.org/rfc/rfc3339.txt](https://www.ietf.org/rfc/rfc3339.txt).
//
// # Examples
//
// Example 1: Compute Timestamp from POSIX `time()`.
//
// Timestamp timestamp;
// timestamp.set_seconds(time(NULL));
// timestamp.set_nanos(0);
//
// Example 2: Compute Timestamp from POSIX `gettimeofday()`.
//
// struct timeval tv;
// gettimeofday(&tv, NULL);
//
// Timestamp timestamp;
// timestamp.set_seconds(tv.tv_sec);
// timestamp.set_nanos(tv.tv_usec * 1000);
//
// Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`.
//
// FILETIME ft;
// GetSystemTimeAsFileTime(&ft);
// UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
//
// // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z
// // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z.
// Timestamp timestamp;
// timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL));
// timestamp.set_nanos((INT32) ((ticks % 10000000) * 100));
//
// Example 4: Compute Timestamp from Java `System.currentTimeMillis()`.
//
// long millis = System.currentTimeMillis();
//
// Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)
// .setNanos((int) ((millis % 1000) * 1000000)).build();
//
//
// Example 5: Compute Timestamp from current time in Python.
//
// timestamp = Timestamp()
// timestamp.GetCurrentTime()
//
// # JSON Mapping
//
// In JSON format, the Timestamp type is encoded as a string in the
// [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the
// format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z"
// where {year} is always expressed using four digits while {month}, {day},
// {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional
// seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution),
// are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone
// is required, though only UTC (as indicated by "Z") is presently supported.
//
// For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past
// 01:30 UTC on January 15, 2017.
//
// In JavaScript, one can convert a Date object to this format using the
// standard [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString]
// method. In Python, a standard `datetime.datetime` object can be converted
// to this format using [`strftime`](https://docs.python.org/2/library/time.html#time.strftime)
// with the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one
// can use the Joda Time's [`ISODateTimeFormat.dateTime()`](
// http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime--)
// to obtain a formatter capable of generating timestamps in this format.
//
//
message Timestamp {
// Represents seconds of UTC time since Unix epoch
// 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
// 9999-12-31T23:59:59Z inclusive.
int64 seconds = 1;
// Non-negative fractions of a second at nanosecond resolution. Negative
// second values with fractions must still have non-negative nanos values
// that count forward in time. Must be from 0 to 999,999,999
// inclusive.
int32 nanos = 2;
}

View File

@ -1,27 +0,0 @@
Copyright 2016, Google Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,24 +0,0 @@
Google API Extensions for Go
============================
[![Build Status](https://travis-ci.org/googleapis/gax-go.svg?branch=master)](https://travis-ci.org/googleapis/gax-go)
[![Code Coverage](https://img.shields.io/codecov/c/github/googleapis/gax-go.svg)](https://codecov.io/github/googleapis/gax-go)
Google API Extensions for Go (gax-go) is a set of modules which aids the
development of APIs for clients and servers based on `gRPC` and Google API
conventions.
Application code will rarely need to use this library directly,
but the code generated automatically from API definition files can use it
to simplify code generation and to provide more convenient and idiomatic API surface.
**This project is currently experimental and not supported.**
Go Versions
===========
This library requires Go 1.6 or above.
License
=======
BSD - please see [LICENSE](https://github.com/googleapis/gax-go/blob/master/LICENSE)
for more information.

View File

@ -1,136 +0,0 @@
// Copyright 2016, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package gax
import (
"math/rand"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
)
// CallOption is an option used by Invoke to control behaviors of RPC calls.
// CallOption works by modifying relevant fields of CallSettings.
type CallOption interface {
// Resolve applies the option by modifying cs.
Resolve(cs *CallSettings)
}
// Retryer is used by Invoke to determine retry behavior.
type Retryer interface {
// Retry reports whether a request should be retriedand how long to pause before retrying
// if the previous attempt returned with err. Invoke never calls Retry with nil error.
Retry(err error) (pause time.Duration, shouldRetry bool)
}
type retryerOption func() Retryer
func (o retryerOption) Resolve(s *CallSettings) {
s.Retry = o
}
// WithRetry sets CallSettings.Retry to fn.
func WithRetry(fn func() Retryer) CallOption {
return retryerOption(fn)
}
// OnCodes returns a Retryer that retries if and only if
// the previous attempt returns a GRPC error whose error code is stored in cc.
// Pause times between retries are specified by bo.
//
// bo is only used for its parameters; each Retryer has its own copy.
func OnCodes(cc []codes.Code, bo Backoff) Retryer {
return &boRetryer{
backoff: bo,
codes: append([]codes.Code(nil), cc...),
}
}
type boRetryer struct {
backoff Backoff
codes []codes.Code
}
func (r *boRetryer) Retry(err error) (time.Duration, bool) {
c := grpc.Code(err)
for _, rc := range r.codes {
if c == rc {
return r.backoff.Pause(), true
}
}
return 0, false
}
// Backoff implements exponential backoff.
// The wait time between retries is a random value between 0 and the "retry envelope".
// The envelope starts at Initial and increases by the factor of Multiplier every retry,
// but is capped at Max.
type Backoff struct {
// Initial is the initial value of the retry envelope, defaults to 1 second.
Initial time.Duration
// Max is the maximum value of the retry envelope, defaults to 30 seconds.
Max time.Duration
// Multiplier is the factor by which the retry envelope increases.
// It should be greater than 1 and defaults to 2.
Multiplier float64
// cur is the current retry envelope
cur time.Duration
}
func (bo *Backoff) Pause() time.Duration {
if bo.Initial == 0 {
bo.Initial = time.Second
}
if bo.cur == 0 {
bo.cur = bo.Initial
}
if bo.Max == 0 {
bo.Max = 30 * time.Second
}
if bo.Multiplier < 1 {
bo.Multiplier = 2
}
d := time.Duration(rand.Int63n(int64(bo.cur)))
bo.cur = time.Duration(float64(bo.cur) * bo.Multiplier)
if bo.cur > bo.Max {
bo.cur = bo.Max
}
return d
}
type CallSettings struct {
// Retry returns a Retryer to be used to control retry logic of a method call.
// If Retry is nil or the returned Retryer is nil, the call will not be retried.
Retry func() Retryer
}

View File

@ -1,40 +0,0 @@
// Copyright 2016, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Package gax contains a set of modules which aid the development of APIs
// for clients and servers based on gRPC and Google API conventions.
//
// Application code will rarely need to use this library directly.
// However, code generated automatically from API definition files can use it
// to simplify code generation and to provide more convenient and idiomatic API surfaces.
//
// This project is currently experimental and not supported.
package gax
const Version = "0.1.0"

View File

@ -1,24 +0,0 @@
package gax
import "bytes"
// XGoogHeader is for use by the Google Cloud Libraries only.
//
// XGoogHeader formats key-value pairs.
// The resulting string is suitable for x-goog-api-client header.
func XGoogHeader(keyval ...string) string {
if len(keyval) == 0 {
return ""
}
if len(keyval)%2 != 0 {
panic("gax.Header: odd argument count")
}
var buf bytes.Buffer
for i := 0; i < len(keyval); i += 2 {
buf.WriteByte(' ')
buf.WriteString(keyval[i])
buf.WriteByte('/')
buf.WriteString(keyval[i+1])
}
return buf.String()[1:]
}

View File

@ -1,90 +0,0 @@
// Copyright 2016, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package gax
import (
"time"
"golang.org/x/net/context"
)
// A user defined call stub.
type APICall func(context.Context) error
// Invoke calls the given APICall,
// performing retries as specified by opts, if any.
func Invoke(ctx context.Context, call APICall, opts ...CallOption) error {
var settings CallSettings
for _, opt := range opts {
opt.Resolve(&settings)
}
return invoke(ctx, call, settings, Sleep)
}
// Sleep is similar to time.Sleep, but it can be interrupted by ctx.Done() closing.
// If interrupted, Sleep returns ctx.Err().
func Sleep(ctx context.Context, d time.Duration) error {
t := time.NewTimer(d)
select {
case <-ctx.Done():
t.Stop()
return ctx.Err()
case <-t.C:
return nil
}
}
type sleeper func(ctx context.Context, d time.Duration) error
// invoke implements Invoke, taking an additional sleeper argument for testing.
func invoke(ctx context.Context, call APICall, settings CallSettings, sp sleeper) error {
var retryer Retryer
for {
err := call(ctx)
if err == nil {
return nil
}
if settings.Retry == nil {
return err
}
if retryer == nil {
if r := settings.Retry(); r != nil {
retryer = r
} else {
return err
}
}
if d, ok := retryer.Retry(err); !ok {
return err
} else if err = sp(ctx, d); err != nil {
return err
}
}
}

View File

@ -1,176 +0,0 @@
// Copyright 2016, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package gax
import (
"errors"
"fmt"
"strings"
)
type matcher interface {
match([]string) (int, error)
String() string
}
type segment struct {
matcher
name string
}
type labelMatcher string
func (ls labelMatcher) match(segments []string) (int, error) {
if len(segments) == 0 {
return 0, fmt.Errorf("expected %s but no more segments found", ls)
}
if segments[0] != string(ls) {
return 0, fmt.Errorf("expected %s but got %s", ls, segments[0])
}
return 1, nil
}
func (ls labelMatcher) String() string {
return string(ls)
}
type wildcardMatcher int
func (wm wildcardMatcher) match(segments []string) (int, error) {
if len(segments) == 0 {
return 0, errors.New("no more segments found")
}
return 1, nil
}
func (wm wildcardMatcher) String() string {
return "*"
}
type pathWildcardMatcher int
func (pwm pathWildcardMatcher) match(segments []string) (int, error) {
length := len(segments) - int(pwm)
if length <= 0 {
return 0, errors.New("not sufficient segments are supplied for path wildcard")
}
return length, nil
}
func (pwm pathWildcardMatcher) String() string {
return "**"
}
type ParseError struct {
Pos int
Template string
Message string
}
func (pe ParseError) Error() string {
return fmt.Sprintf("at %d of template '%s', %s", pe.Pos, pe.Template, pe.Message)
}
// PathTemplate manages the template to build and match with paths used
// by API services. It holds a template and variable names in it, and
// it can extract matched patterns from a path string or build a path
// string from a binding.
//
// See http.proto in github.com/googleapis/googleapis/ for the details of
// the template syntax.
type PathTemplate struct {
segments []segment
}
// NewPathTemplate parses a path template, and returns a PathTemplate
// instance if successful.
func NewPathTemplate(template string) (*PathTemplate, error) {
return parsePathTemplate(template)
}
// MustCompilePathTemplate is like NewPathTemplate but panics if the
// expression cannot be parsed. It simplifies safe initialization of
// global variables holding compiled regular expressions.
func MustCompilePathTemplate(template string) *PathTemplate {
pt, err := NewPathTemplate(template)
if err != nil {
panic(err)
}
return pt
}
// Match attempts to match the given path with the template, and returns
// the mapping of the variable name to the matched pattern string.
func (pt *PathTemplate) Match(path string) (map[string]string, error) {
paths := strings.Split(path, "/")
values := map[string]string{}
for _, segment := range pt.segments {
length, err := segment.match(paths)
if err != nil {
return nil, err
}
if segment.name != "" {
value := strings.Join(paths[:length], "/")
if oldValue, ok := values[segment.name]; ok {
values[segment.name] = oldValue + "/" + value
} else {
values[segment.name] = value
}
}
paths = paths[length:]
}
if len(paths) != 0 {
return nil, fmt.Errorf("Trailing path %s remains after the matching", strings.Join(paths, "/"))
}
return values, nil
}
// Render creates a path string from its template and the binding from
// the variable name to the value.
func (pt *PathTemplate) Render(binding map[string]string) (string, error) {
result := make([]string, 0, len(pt.segments))
var lastVariableName string
for _, segment := range pt.segments {
name := segment.name
if lastVariableName != "" && name == lastVariableName {
continue
}
lastVariableName = name
if name == "" {
result = append(result, segment.String())
} else if value, ok := binding[name]; ok {
result = append(result, value)
} else {
return "", fmt.Errorf("%s is not found", name)
}
}
built := strings.Join(result, "/")
return built, nil
}

View File

@ -1,227 +0,0 @@
// Copyright 2016, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package gax
import (
"fmt"
"io"
"strings"
)
// This parser follows the syntax of path templates, from
// https://github.com/googleapis/googleapis/blob/master/google/api/http.proto.
// The differences are that there is no custom verb, we allow the initial slash
// to be absent, and that we are not strict as
// https://tools.ietf.org/html/rfc6570 about the characters in identifiers and
// literals.
type pathTemplateParser struct {
r *strings.Reader
runeCount int // the number of the current rune in the original string
nextVar int // the number to use for the next unnamed variable
seenName map[string]bool // names we've seen already
seenPathWildcard bool // have we seen "**" already?
}
func parsePathTemplate(template string) (pt *PathTemplate, err error) {
p := &pathTemplateParser{
r: strings.NewReader(template),
seenName: map[string]bool{},
}
// Handle panics with strings like errors.
// See pathTemplateParser.error, below.
defer func() {
if x := recover(); x != nil {
errmsg, ok := x.(errString)
if !ok {
panic(x)
}
pt = nil
err = ParseError{p.runeCount, template, string(errmsg)}
}
}()
segs := p.template()
// If there is a path wildcard, set its length. We can't do this
// until we know how many segments we've got all together.
for i, seg := range segs {
if _, ok := seg.matcher.(pathWildcardMatcher); ok {
segs[i].matcher = pathWildcardMatcher(len(segs) - i - 1)
break
}
}
return &PathTemplate{segments: segs}, nil
}
// Used to indicate errors "thrown" by this parser. We don't use string because
// many parts of the standard library panic with strings.
type errString string
// Terminates parsing immediately with an error.
func (p *pathTemplateParser) error(msg string) {
panic(errString(msg))
}
// Template = [ "/" ] Segments
func (p *pathTemplateParser) template() []segment {
var segs []segment
if p.consume('/') {
// Initial '/' needs an initial empty matcher.
segs = append(segs, segment{matcher: labelMatcher("")})
}
return append(segs, p.segments("")...)
}
// Segments = Segment { "/" Segment }
func (p *pathTemplateParser) segments(name string) []segment {
var segs []segment
for {
subsegs := p.segment(name)
segs = append(segs, subsegs...)
if !p.consume('/') {
break
}
}
return segs
}
// Segment = "*" | "**" | LITERAL | Variable
func (p *pathTemplateParser) segment(name string) []segment {
if p.consume('*') {
if name == "" {
name = fmt.Sprintf("$%d", p.nextVar)
p.nextVar++
}
if p.consume('*') {
if p.seenPathWildcard {
p.error("multiple '**' disallowed")
}
p.seenPathWildcard = true
// We'll change 0 to the right number at the end.
return []segment{{name: name, matcher: pathWildcardMatcher(0)}}
}
return []segment{{name: name, matcher: wildcardMatcher(0)}}
}
if p.consume('{') {
if name != "" {
p.error("recursive named bindings are not allowed")
}
return p.variable()
}
return []segment{{name: name, matcher: labelMatcher(p.literal())}}
}
// Variable = "{" FieldPath [ "=" Segments ] "}"
// "{" is already consumed.
func (p *pathTemplateParser) variable() []segment {
// Simplification: treat FieldPath as LITERAL, instead of IDENT { '.' IDENT }
name := p.literal()
if p.seenName[name] {
p.error(name + " appears multiple times")
}
p.seenName[name] = true
var segs []segment
if p.consume('=') {
segs = p.segments(name)
} else {
// "{var}" is equivalent to "{var=*}"
segs = []segment{{name: name, matcher: wildcardMatcher(0)}}
}
if !p.consume('}') {
p.error("expected '}'")
}
return segs
}
// A literal is any sequence of characters other than a few special ones.
// The list of stop characters is not quite the same as in the template RFC.
func (p *pathTemplateParser) literal() string {
lit := p.consumeUntil("/*}{=")
if lit == "" {
p.error("empty literal")
}
return lit
}
// Read runes until EOF or one of the runes in stopRunes is encountered.
// If the latter, unread the stop rune. Return the accumulated runes as a string.
func (p *pathTemplateParser) consumeUntil(stopRunes string) string {
var runes []rune
for {
r, ok := p.readRune()
if !ok {
break
}
if strings.IndexRune(stopRunes, r) >= 0 {
p.unreadRune()
break
}
runes = append(runes, r)
}
return string(runes)
}
// If the next rune is r, consume it and return true.
// Otherwise, leave the input unchanged and return false.
func (p *pathTemplateParser) consume(r rune) bool {
rr, ok := p.readRune()
if !ok {
return false
}
if r == rr {
return true
}
p.unreadRune()
return false
}
// Read the next rune from the input. Return it.
// The second return value is false at EOF.
func (p *pathTemplateParser) readRune() (rune, bool) {
r, _, err := p.r.ReadRune()
if err == io.EOF {
return r, false
}
if err != nil {
p.error(err.Error())
}
p.runeCount++
return r, true
}
// Put the last rune that was read back on the input.
func (p *pathTemplateParser) unreadRune() {
if err := p.r.UnreadRune(); err != nil {
p.error(err.Error())
}
p.runeCount--
}

View File

@ -0,0 +1,16 @@
Unless explicitly stated at the top of the file, this code is covered
by the following license:
Copyright 2016-2017 The authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,100 @@
This repository contains Go bindings and sample code for [Hyper-V sockets](https://msdn.microsoft.com/en-us/virtualization/hyperv_on_windows/develop/make_mgmt_service) and [virtio sockets](http://stefanha.github.io/virtio/)(VSOCK).
## Organisation
- `pkg/hvsock`: Go binding for Hyper-V sockets
- `pkg/vsock`: Go binding for virtio VSOCK
- `cmd/sock_stress`: A stress test program for virtsock
- `cmd/vsudd`: A unix domain socket to virtsock proxy (used in Docker for Mac/Windows)
- `scripts`: Miscellaneous scripts
- `c`: Sample C code (including benchmarks and stress tests)
- `data`: Data from benchmarks
## Building
By default the Go sample code is build in a container. Simply type `make`.
If you want to build binaries on a local system use `make build-binaries`.
## Testing
There are several examples and tests written both in [Go](./cmd) and in [C](./c). The C code is Hyper-V sockets specific while the Go code also works with virtio sockets and [HyperKit](https://github.com/moby/hyperkit). The respective READMEs contain instructions on how to run the tests, but the simplest way is to use [LinuxKit](https://github.com/linuxkit/linuxkit).
Assuming you have LinuxKit installed, the make target `make linuxkit`
will build a custom Linux image which can be booted on HyperKit or on
Windows. The custom Linux image contains the test binaries.
### macOS
Boot the Linux VM:
```
linuxkit run hvtest
```
This should create a directory called `./hvtest-state`.
Run the server in the VM and client on the host:
```
linux$ sock_stress -s vsock -v 1
macos$ ./bin/sock_stress.darwin -c vsock://3 -m hyperkit:./hvtest-state -v 1
```
Run the server on the host and the client inside the VM:
```
macos$ ./bin/sock_stress.darwin -s vsock -m hyperkit:./hvtest-state -v 1
linux$ sock_stress -c vsock://2 -v 1
```
### Windows
On Windows we currently only support the server to be run inside the
VM and the host connecting to it. In the future we will support
running the server on the host as well.
For Linux guests on Windows there are two different implmentations,
one in the LinuxKit 4.9.x kernels and one in 4.14.x upstream
kernels. They require different protocols to be used. The
`sock_stress` and `vsudd` programs automatically detect which version
to use.
Boot the Linux VM (from an elevated powershell):
```
linuxkit run -name hvtest hvtest-efi.iso
```
Run the server in the VM and client on the host:
```
linux$ sock_stress -v 1 -s hvsock
win$ sock_stress -v 1 -c hvsock://<VM ID>
```
(where `<VM ID>` is from the output of: `(get-vm hvtest).Id`)
Run the server on the host and the client inside the VM:
```
win$ sock_stress -v 1 -s hvsock
linux$ sock_stress -v 1 -c hvsock://parent
```
**Note:** This may fail on the client with receiving unexpected EOFs (see below).
## Known limitations
- `hvsock`: When running the server on the host with a client in a
Linux VM, it looks like unidirectional `shutdown()` is not working
properly. There appears to be a race of sort.
- `hvsock`: Hyper-V socket implementations prior to Windows build
10586 (aka 1511, aka Threshold 2) was buggy. There may even be
issues with build prior to build 14393 (aka 1607, aka Redstone 1).
- `hvsock`: Earlier versions of this code supported the older Windows
builds, but support has now been removed. If you require the older version,
please use the `end_10586_tag`.
- `vsock`: There is general host side implementation as the interface
is hypervisor specific. The `vsock` package includes some support
for connecting with the VSOCK implementation in
[Hyperkit](https://github.com/moby/hyperkit), but there is no
implementation for, e.g. `qemu`.

View File

@ -0,0 +1,48 @@
# Hyper-V sockets sample applications
## Building
Building is driven by `make` (or `mingw32-make.exe`). By default both
Linux and Windows binaries are build.
Linux builds are done via Dockerfiles and create static binaries.
Windows builds require MSBuild, Visual Studio and Windows SDK
installed with a minimum version of 14290. The project/solutions
assumes 14291, so you may have to adjust the
`WindowsTargetPlatformVersion` in the project files.
## Running
All sample application assume a service ID of `3049197C-FACB-11E6-BD58-64006A7986D3` which needs to be registered *once* using:
```
$service = New-Item -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Virtualization\GuestCommunicationServices" -Name 3049197C-FACB-11E6-BD58-64006A7986D3
$service.SetValue("ElementName", "Hyper-V Socket Echo Service")
```
### Echo applications
The echo server is started with:
```
hvecho -s
```
The client supports a number of modes. By default, with no arguments supplied it will connected using loopback mode, i.e. it tries to connect to the server on the same partition:
```
hvecho -c
```
The client can be run in a VM and started with:
```
hvecho -c parent
```
which attempts to connect to the server in the parent partition.
Finally, if the server is run in a VM, the client can be invoked in the parent partition with:
```
client -c <vmid>
```
where `<vmid>` is the GUID of the VM the server is running in. The GUID can be retrieved with: `(get-vm <VM Name>).id`.

View File

@ -0,0 +1,365 @@
/*
* Compatibility layer between Windows and Linux
*/
#ifdef _MSC_VER
#undef UNICODE
#define WIN32_LEAN_AND_MEAN
#define _CRT_SECURE_NO_WARNINGS
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <hvsocket.h>
#pragma comment (lib, "Ws2_32.lib")
#pragma comment (lib, "Mswsock.lib")
#pragma comment (lib, "AdvApi32.lib")
#else /* !_MSC_VER */
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <stdint.h>
#include <time.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/socket.h>
#endif /* !_MSC_VER */
#include <inttypes.h>
#ifdef _MSC_VER
typedef int socklen_t;
typedef __int8 int8_t;
typedef __int16 int16_t;
typedef __int32 int32_t;
typedef __int64 int64_t;
typedef unsigned __int8 uint8_t;
typedef unsigned __int16 uint16_t;
typedef unsigned __int32 uint32_t;
typedef unsigned __int64 uint64_t;
#endif
#ifndef _MSC_VER
/* Compat layer for Linux/Unix */
typedef int SOCKET;
#ifndef SOCKET_ERROR
#define SOCKET_ERROR -1
#endif
#ifndef INVALID_SOCKET
#define INVALID_SOCKET -1
#endif
#define closesocket(_fd) close(_fd)
/* Shutdown flags are different too */
#define SD_SEND SHUT_WR
#define SD_RECEIVE SHUT_RD
#define SD_BOTH SHUT_RDWR
#define __cdecl
/* GUID handling */
typedef struct _GUID {
uint32_t Data1;
uint16_t Data2;
uint16_t Data3;
uint8_t Data4[8];
} GUID;
#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
const GUID name = {l, w1, w2, {b1, b2, b3, b4, b5, b6, b7, b8}}
/* HV Socket definitions */
#define AF_HYPERV 43
#define HV_PROTOCOL_RAW 1
typedef struct _SOCKADDR_HV
{
unsigned short Family;
unsigned short Reserved;
GUID VmId;
GUID ServiceId;
} SOCKADDR_HV;
DEFINE_GUID(HV_GUID_ZERO,
0x00000000, 0x0000, 0x0000, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
DEFINE_GUID(HV_GUID_BROADCAST,
0xFFFFFFFF, 0xFFFF, 0xFFFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF);
DEFINE_GUID(HV_GUID_WILDCARD,
0x00000000, 0x0000, 0x0000, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
DEFINE_GUID(HV_GUID_CHILDREN,
0x90db8b89, 0x0d35, 0x4f79, 0x8c, 0xe9, 0x49, 0xea, 0x0a, 0xc8, 0xb7, 0xcd);
DEFINE_GUID(HV_GUID_LOOPBACK,
0xe0e16197, 0xdd56, 0x4a10, 0x91, 0x95, 0x5e, 0xe7, 0xa1, 0x55, 0xa8, 0x38);
DEFINE_GUID(HV_GUID_PARENT,
0xa42e7cda, 0xd03f, 0x480c, 0x9c, 0xc2, 0xa4, 0xde, 0x20, 0xab, 0xb8, 0x78);
#endif /* !_MSC_VER */
/* Common definitions (only valid on Linux, though) */
#ifndef AF_VSOCK
#define AF_VSOCK 40
#endif
#ifndef VMADDR_CID_ANY
#define VMADDR_CID_ANY -1U
#endif
#ifndef VMADDR_CID_HYPERVISOR
#define VMADDR_CID_HYPERVISOR 0
#endif
#ifndef VMADDR_CID_RESERVED
#define VMADDR_CID_RESERVED 1
#endif
#ifndef VMADDR_CID_HOST
#define VMADDR_CID_HOST 2
#endif
typedef struct _SOCKADDR_VM
{
unsigned short Family;
unsigned short Reserved;
unsigned int SvmPort;
unsigned int SvmCID;
#ifndef _MSC_VER
unsigned char svm_zero[sizeof(struct sockaddr) -
sizeof(sa_family_t) - sizeof(unsigned short) -
sizeof(unsigned int) - sizeof(unsigned int)];
#endif
} SOCKADDR_VM;
/* Thread wrappers */
#ifdef _MSC_VER
typedef HANDLE THREAD_HANDLE;
static inline int thread_create(THREAD_HANDLE *t, void *(*f)(void *), void *arg)
{
*t = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)f, arg, 0, NULL);
return 0;
}
static inline int thread_join(THREAD_HANDLE t)
{
WaitForSingleObject(t, INFINITE);
return 0;
}
static inline int thread_detach(THREAD_HANDLE t)
{
return CloseHandle(t);
}
#else
#include <pthread.h>
typedef pthread_t THREAD_HANDLE;
static inline int thread_create(THREAD_HANDLE *t, void *(*f)(void *), void *arg)
{
return pthread_create(t, NULL, f, arg);
}
static inline int thread_join(THREAD_HANDLE t)
{
return pthread_join(t, NULL);
}
static inline int thread_detach(THREAD_HANDLE t)
{
return pthread_detach(t);
}
#endif
/* Time wrappers */
#ifdef _MSC_VER
static inline uint64_t time_ns(void)
{
LARGE_INTEGER t, freq;
QueryPerformanceFrequency(&freq);
QueryPerformanceCounter(&t);
t.QuadPart *= 1000000000;
return (uint64_t)t.QuadPart / freq.QuadPart;
}
static inline unsigned int sleep(unsigned int sec)
{
Sleep(sec * 1000);
return 0;
}
#else
static inline uint64_t time_ns(void)
{
struct timespec ts;
int ret;
ret = clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
if (ret)
return 0;
/* We don't really mind if this overflows...There are plenty of bits */
return (uint64_t)ts.tv_sec * 1000000000 + ts.tv_nsec;
}
#endif
/*
* Finally some common utility macros and functions
*/
#include <stdio.h>
#include <string.h>
#define GUID_FMT "%08x-%04hx-%04hx-%02x%02x-%02x%02x%02x%02x%02x%02x"
#define GUID_ARGS(_g) \
(_g).Data1, (_g).Data2, (_g).Data3, \
(_g).Data4[0], (_g).Data4[1], (_g).Data4[2], (_g).Data4[3], \
(_g).Data4[4], (_g).Data4[5], (_g).Data4[6], (_g).Data4[7]
#define GUID_SARGS(_g) \
&(_g).Data1, &(_g).Data2, &(_g).Data3, \
&(_g).Data4[0], &(_g).Data4[1], &(_g).Data4[2], &(_g).Data4[3], \
&(_g).Data4[4], &(_g).Data4[5], &(_g).Data4[6], &(_g).Data4[7]
static inline int parseguid(const char *s, GUID *g)
{
int res;
int p0, p1, p2, p3, p4, p5, p6, p7;
res = sscanf(s, GUID_FMT,
&g->Data1, &g->Data2, &g->Data3,
&p0, &p1, &p2, &p3, &p4, &p5, &p6, &p7);
if (res != 11)
return 1;
g->Data4[0] = p0;
g->Data4[1] = p1;
g->Data4[2] = p2;
g->Data4[3] = p3;
g->Data4[4] = p4;
g->Data4[5] = p5;
g->Data4[6] = p6;
g->Data4[7] = p7;
return 0;
}
/* Slightly different error handling between Windows and Linux */
static inline void sockerr(const char *msg)
{
#ifdef _MSC_VER
fprintf(stderr, "%s Error: %d\n", msg, WSAGetLastError());
#else
fprintf(stderr, "%s Error: %d. %s\n", msg, errno, strerror(errno));
#endif
}
/* poll wrappers */
/* Set socket to non-blocking */
static inline int poll_enable(SOCKET s)
{
int ret;
#ifdef _MSC_VER
unsigned long mode = 1;
ret = ioctlsocket(s, FIONBIO, &mode);
#else
int flags;
flags = fcntl(s, F_GETFL, 0);
if (flags < 0)
return flags;
ret = fcntl(s, F_SETFL, flags | O_NONBLOCK);
#endif
return ret;
}
/* Set socket to non-blocking */
static inline int poll_disable(SOCKET s)
{
int ret;
#ifdef _MSC_VER
unsigned long mode = 0;
ret = ioctlsocket(s, FIONBIO, &mode);
#else
int flags;
flags = fcntl(s, F_GETFL, 0);
if (flags < 0)
return flags;
ret = fcntl(s, F_SETFL, flags & ~O_NONBLOCK);
#endif
return ret;
}
/* Return true if we should poll */
static inline int poll_check()
{
#ifdef _MSC_VER
int err = WSAGetLastError();
return err == WSAEWOULDBLOCK || err == WSAEFAULT;
#else
return errno == EWOULDBLOCK || errno == EAGAIN;
#endif
}
#ifdef _MSC_VER
static inline int poll(struct pollfd fds[], unsigned long nfds, int timeout)
{
return WSAPoll(fds, nfds, timeout);
}
#endif
/* Connect with timeout (in milliseconds), different to WinSock ConnectEx() */
static inline int connect_ex(int s, const struct sockaddr *sa,
socklen_t len, int timeout)
{
struct timeval tv;
fd_set fdset;
int ret;
ret = poll_enable(s);
if (ret < 0)
return ret;
ret = connect(s, sa, len);
if (!ret)
goto out; /* Connected */
/* Got an error, see if we should select() */
#ifdef _MSC_VER
ret = WSAGetLastError();
if (ret != WSAEWOULDBLOCK) {
ret = SOCKET_ERROR;
goto out;
}
#else
if (errno != EINPROGRESS)
goto out;
#endif
FD_ZERO(&fdset);
FD_SET(s, &fdset);
tv.tv_sec = 0;
tv.tv_usec = timeout * 1000;
ret = select(s + 1, NULL, &fdset, NULL, &tv);
if (ret != 1) {
ret = SOCKET_ERROR;
goto out;
}
/* Check status */
ret = 0;
len = sizeof(ret);
/* char * is for windows... */
getsockopt(s, SOL_SOCKET, SO_ERROR, (char *)&ret, &len);
if (ret != 0)
ret = SOCKET_ERROR;
out:
poll_disable(s);
return ret;
}

View File

@ -0,0 +1,552 @@
/*
* A Hyper-V socket benchmarking program
*/
#include "compat.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* 3049197C-FACB-11E6-BD58-64006A7986D3 */
DEFINE_GUID(BM_GUID,
0x3049197c, 0xfacb, 0x11e6, 0xbd, 0x58, 0x64, 0x00, 0x6a, 0x79, 0x86, 0xd3);
#define BM_PORT 0x3049197c
#ifdef _MSC_VER
static WSADATA wsaData;
#endif
#ifndef ARRAY_SIZE
#define ARRAY_SIZE(_arr) (sizeof(_arr)/sizeof(*(_arr)))
#endif
/* Use a static buffer for send and receive. */
#define MAX_BUF_LEN (2 * 1024 * 1024)
static char buf[MAX_BUF_LEN];
/* Time (in ns) to run eeach bandwidth test */
#define BM_BW_TIME (10ULL * 1000 * 1000 * 1000)
/* How many connections to make */
#define BM_CONNS 2000
static int verbose;
#define INFO(...) \
do { \
if (verbose) { \
printf(__VA_ARGS__); \
fflush(stdout); \
} \
} while (0)
#define DBG(...) \
do { \
if (verbose > 1) { \
printf(__VA_ARGS__); \
fflush(stdout); \
} \
} while (0)
#define TRC(...) \
do { \
if (verbose > 2) { \
printf(__VA_ARGS__); \
fflush(stdout); \
} \
} while (0)
enum benchmark {
BM_BW_UNI = 1, /* Uni-directional Bandwidth benchamrk */
BM_LAT = 2, /* Message ping-pong latency over single connection */
BM_CONN = 3, /* Connection benchmark */
};
/* There's anecdotal evidence that a blocking send()/recv() is slower
* than performing non-blocking send()/recv() calls and then use
* epoll()/WSAPoll(). This flags switches between the two
*/
static int opt_poll;
/* Use the vsock interface on Linux */
static int opt_vsock;
/* Bandwidth tests:
*
* The TX side sends a fixed amount of data in fixed sized
* messages. The RX side drains the ring in message sized chunks (or less).
*/
static int bw_rx(SOCKET fd, int msg_sz)
{
struct pollfd pfd = { 0 };
int rx_sz;
int ret;
if (opt_poll) {
pfd.fd = fd;
pfd.events = POLLIN;
}
rx_sz = msg_sz ? msg_sz : ARRAY_SIZE(buf);
DBG("bw_rx: msg_sz=%d rx_sz=%d\n", msg_sz, rx_sz);
for (;;) {
ret = recv(fd, buf, rx_sz, 0);
if (ret == 0) {
break;
} else if (ret == SOCKET_ERROR) {
if (opt_poll && poll_check()) {
pfd.revents = 0;
poll(&pfd, 1, -1); /* XXX no error checking */
continue;
}
sockerr("recv()");
ret = -1;
goto err_out;
}
TRC("Received: %d\n", ret);
}
ret = 0;
err_out:
return ret;
}
static int bw_tx(SOCKET fd, int msg_sz, uint64_t *bw)
{
struct pollfd pfd = { 0 };
uint64_t start, end, diff;
int msgs_sent = 0;
int tx_sz;
int sent;
int ret;
if (opt_poll) {
pfd.fd = fd;
pfd.events = POLLOUT;
}
tx_sz = msg_sz ? msg_sz : ARRAY_SIZE(buf);
DBG("bw_tx: msg_sz=%d tx_sz=%d \n", msg_sz, tx_sz);
start = time_ns();
end = time_ns();
while (end < start + BM_BW_TIME) {
sent = 0;
while (sent < tx_sz) {
ret = send(fd, buf + sent, tx_sz - sent, 0);
if (ret == SOCKET_ERROR) {
if (opt_poll && poll_check()) {
pfd.revents = 0;
poll(&pfd, 1, -1); /* XXX no error checking */
continue;
}
sockerr("send()");
ret = -1;
goto err_out;
}
sent += ret;
TRC("Sent: %d %d\n", sent, ret);
}
msgs_sent++;
if (!(msgs_sent % 1000))
end = time_ns();
}
DBG("bw_tx: %d %"PRIu64" %"PRIu64"\n", msgs_sent, start, end);
/* Bandwidth in Mbits per second */
diff = end - start;
diff /= 1000 * 1000; /* Time in milliseconds */
*bw = (8ULL * msgs_sent * msg_sz * 1000) / (diff * 1024 * 1024);
ret = 0;
err_out:
return ret;
}
/*
* Main server and client entry points
*/
static int server(int bm, int msg_sz)
{
SOCKET lsock, csock;
SOCKADDR_VM savm, sacvm;
SOCKADDR_HV sahv, sachv;
socklen_t socklen;
int max_conn;
int ret = 0;
INFO("server: bm=%d msg_sz=%d\n", bm, msg_sz);
if (opt_vsock)
lsock = socket(AF_VSOCK, SOCK_STREAM, 0);
else
lsock = socket(AF_HYPERV, SOCK_STREAM, HV_PROTOCOL_RAW);
if (lsock == INVALID_SOCKET) {
sockerr("socket()");
return 1;
}
memset(&savm, 0, sizeof(savm));
savm.Family = AF_VSOCK;
savm.SvmCID = VMADDR_CID_ANY;
savm.SvmPort = BM_PORT;
memset(&sahv, 0, sizeof(sahv));
sahv.Family = AF_HYPERV;
sahv.VmId = HV_GUID_WILDCARD;
sahv.ServiceId = BM_GUID;
if (opt_vsock)
ret = bind(lsock, (const struct sockaddr *)&savm, sizeof(savm));
else
ret = bind(lsock, (const struct sockaddr *)&sahv, sizeof(sahv));
if (ret == SOCKET_ERROR) {
sockerr("bind()");
closesocket(lsock);
return 1;
}
ret = listen(lsock, SOMAXCONN);
if (ret == SOCKET_ERROR) {
sockerr("listen()");
goto err_out;
}
INFO("server: listening\n");
if (bm == BM_CONN)
max_conn = BM_CONNS;
else
max_conn = 1;
while (max_conn) {
max_conn--;
memset(&sacvm, 0, sizeof(sacvm));
memset(&sachv, 0, sizeof(sachv));
if (opt_vsock) {
socklen = sizeof(sacvm);
csock = accept(lsock, (struct sockaddr *)&sacvm, &socklen);
} else {
socklen = sizeof(sachv);
csock = accept(lsock, (struct sockaddr *)&sachv, &socklen);
}
if (csock == INVALID_SOCKET) {
sockerr("accept()");
ret = -1;
continue;
}
INFO("server: accepted\n");
/* Switch to non-blocking if we want to poll */
if (opt_poll)
poll_enable(csock);
ret = bw_rx(csock, msg_sz);
closesocket(csock);
}
err_out:
closesocket(lsock);
return ret;
}
static int client(GUID target_guid, unsigned int target_cid,
int bm, int msg_sz)
{
SOCKET fd;
SOCKADDR_VM savm;
SOCKADDR_HV sahv;
uint64_t res;
int ret = 0;
INFO("client: bm=%d msg_sz=%d\n", bm, msg_sz);
if (opt_vsock)
fd = socket(AF_VSOCK, SOCK_STREAM, 0);
else
fd = socket(AF_HYPERV, SOCK_STREAM, HV_PROTOCOL_RAW);
if (fd == INVALID_SOCKET) {
sockerr("socket()");
return 1;
}
memset(&sahv, 0, sizeof(sahv));
savm.Family = AF_VSOCK;
savm.SvmCID = target_cid;
savm.SvmPort = BM_PORT;
memset(&sahv, 0, sizeof(sahv));
sahv.Family = AF_HYPERV;
sahv.VmId = target_guid;
sahv.ServiceId = BM_GUID;
if (opt_vsock)
ret = connect(fd, (const struct sockaddr *)&savm, sizeof(savm));
else
ret = connect(fd, (const struct sockaddr *)&sahv, sizeof(sahv));
if (ret == SOCKET_ERROR) {
sockerr("connect()");
ret = -1;
goto err_out;
}
INFO("client: connected\n");
/* Switch to non-blocking if we want to poll */
if (opt_poll)
poll_enable(fd);
if (bm == BM_BW_UNI) {
ret = bw_tx(fd, msg_sz, &res);
if (ret)
goto err_out;
printf("%d %"PRIu64"\n", msg_sz, res);
} else {
fprintf(stderr, "Unknown benchmark %d\n", bm);
ret = -1;
}
err_out:
closesocket(fd);
return ret;
}
/* Different client for connection tests */
#define BM_CONN_TIMEOUT 500 /* 500ms */
static int client_conn(GUID target_guid, unsigned int target_cid)
{
uint64_t start, end, diff;
int histogram[3 * 9 + 3];
SOCKADDR_VM savm;
SOCKADDR_HV sahv;
SOCKET fd;
int sum;
int ret;
int i;
memset(histogram, 0, sizeof(histogram));
INFO("client: connection test\n");
for (i = 0; i < BM_CONNS; i++) {
if (opt_vsock)
fd = socket(AF_VSOCK, SOCK_STREAM, 0);
else
fd = socket(AF_HYPERV, SOCK_STREAM, HV_PROTOCOL_RAW);
if (fd == INVALID_SOCKET) {
histogram[ARRAY_SIZE(histogram) - 1] += 1;
DBG("conn: %d -> socket error\n", i);
continue;
}
memset(&sahv, 0, sizeof(sahv));
savm.Family = AF_VSOCK;
savm.SvmCID = target_cid;
savm.SvmPort = BM_PORT;
memset(&sahv, 0, sizeof(sahv));
sahv.Family = AF_HYPERV;
sahv.VmId = target_guid;
sahv.ServiceId = BM_GUID;
start = time_ns();
if (opt_poll)
if (opt_vsock)
ret = connect_ex(fd, (const struct sockaddr *)&savm, sizeof(savm),
BM_CONN_TIMEOUT);
else
ret = connect_ex(fd, (const struct sockaddr *)&sahv, sizeof(sahv),
BM_CONN_TIMEOUT);
else
if (opt_vsock)
ret = connect(fd, (const struct sockaddr *)&savm, sizeof(savm));
else
ret = connect(fd, (const struct sockaddr *)&sahv, sizeof(sahv));
if (ret == SOCKET_ERROR) {
histogram[ARRAY_SIZE(histogram) - 2] += 1;
DBG("conn: %d -> connect error\n", i);
} else {
end = time_ns();
diff = (end - start);
DBG("conn: %d -> %"PRIu64"ns\n", i, diff);
diff /= (1000 * 1000);
if (diff < 10)
histogram[diff] += 1;
else if (diff < 100)
histogram[9 + diff / 10] += 1;
else if (diff < 1000)
histogram[18 + diff / 100] += 1;
else
histogram[ARRAY_SIZE(histogram) - 3] += 1;
}
closesocket(fd);
}
/* Print the results */
printf("# time (ms) vs count vs cumulative percent\n");
sum = 0;
for (i = 0; i < ARRAY_SIZE(histogram); i++) {
sum += histogram[i];
if (i < 9)
printf("%d %d %6.2f\n", i + 1, histogram[i],
sum * 100.0 / BM_CONNS);
else if (i < 18)
printf("%d %d %6.2f\n", (i - 9 + 1) * 10, histogram[i],
sum * 100.0 / BM_CONNS);
else if (i < 27)
printf("%d %d %6.2f\n", (i - 18 + 1) * 100, histogram[i],
sum * 100.0 / BM_CONNS);
else if (i == ARRAY_SIZE(histogram) - 3)
printf(">=%d %d %6.2f\n", (i - 27 + 1) * 1000, histogram[i],
sum * 100.0 / BM_CONNS);
else if (i == ARRAY_SIZE(histogram) - 2)
printf("connect_err %d %6.2f\n", histogram[i],
sum * 100.0 / BM_CONNS);
else
printf("socket_err %d %6.2f\n", histogram[i],
sum * 100.0 / BM_CONNS);
}
return 0;
}
void usage(char *name)
{
printf("%s: -s|-c <carg> -b|-l -m <sz> [-v]\n", name);
printf(" -s Server mode\n");
printf(" -c <carg> Client mode. <carg>:\n");
printf(" 'loopback': Connect in loopback mode\n");
printf(" 'parent': Connect to the parent partition\n");
printf(" <guid>: Connect to VM with GUID\n");
printf("\n");
printf(" -B Bandwidth test\n");
printf(" -L Latency test\n");
printf(" -C Connection test\n");
printf("\n");
printf(" -vsock Use vsock (Linux only)\n");
printf(" -m <sz> Message size in bytes\n");
printf(" -p Use poll instead of blocking send()/recv()\n");
printf(" -v Verbose output\n");
}
int __cdecl main(int argc, char **argv)
{
int opt_server = 0;
int opt_bm = 0;
int opt_msgsz = 0;
GUID target_guid;
unsigned int target_cid;
char *target_str = NULL;
int res = 0;
int i;
#ifdef _MSC_VER
/* Initialize Winsock */
res = WSAStartup(MAKEWORD(2,2), &wsaData);
if (res != 0) {
fprintf(stderr, "WSAStartup() failed with error: %d\n", res);
return 1;
}
#endif
/* No getopt on windows. Do some manual parsing */
for (i = 1; i < argc; i++) {
if (strcmp(argv[i], "-s") == 0) {
opt_server = 1;
} else if (strcmp(argv[i], "-c") == 0) {
opt_server = 0;
if (i + 1 >= argc) {
fprintf(stderr, "-c requires an argument\n");
usage(argv[0]);
goto out;
}
if (strcmp(argv[i + 1], "loopback") == 0) {
target_guid = HV_GUID_LOOPBACK;
target_cid = VMADDR_CID_HYPERVISOR;
} else if (strcmp(argv[i + 1], "parent") == 0) {
target_guid = HV_GUID_PARENT;
target_cid = VMADDR_CID_HOST;
} else {
target_str = argv[i + 1];
}
i++;
} else if (strcmp(argv[i], "-B") == 0) {
opt_bm = BM_BW_UNI;
} else if (strcmp(argv[i], "-L") == 0) {
opt_bm = BM_LAT;
} else if (strcmp(argv[i], "-C") == 0) {
opt_bm = BM_CONN;
} else if (strcmp(argv[i], "-vsock") == 0) {
opt_vsock = 1;
} else if (strcmp(argv[i], "-m") == 0) {
if (i + 1 >= argc) {
fprintf(stderr, "-m requires an argument\n");
usage(argv[0]);
goto out;
}
opt_msgsz = atoi(argv[++i]);
} else if (strcmp(argv[i], "-p") == 0) {
opt_poll = 1;
} else if (strcmp(argv[i], "-v") == 0) {
verbose++;
} else {
usage(argv[0]);
goto out;
}
}
#ifdef _MSC_VER
if (opt_vsock) {
fprintf(stderr, "-vsock is not valid on Windows\n");
goto out;
}
#endif
if (target_str) {
if (opt_vsock) {
target_cid = strtoul(target_str, NULL, 0);
} else {
res = parseguid(target_str, &target_guid);
if (res != 0) {
fprintf(stderr, "failed to scan: %s\n", target_str);
goto out;
}
}
}
if (!opt_bm) {
fprintf(stderr, "You need to specify a test\n");
goto out;
}
if (opt_bm == BM_LAT) {
fprintf(stderr, "Latency tests currently not implemented\n");
goto out;
}
if (opt_server) {
res = server(opt_bm, opt_msgsz);
} else {
if (opt_bm == BM_CONN)
res = client_conn(target_guid, target_cid);
else
res = client(target_guid, target_cid, opt_bm, opt_msgsz);
}
out:
#ifdef _MSC_VER
WSACleanup();
#endif
return res;
}

View File

@ -0,0 +1,331 @@
/*
* A simple Echo server and client using Hyper-V sockets
*
* Works on Linux and Windows (kinda)
*
* This was primarily written to checkout shutdown(), which turns out
* does not work.
*/
#include "compat.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* 3049197C-FACB-11E6-BD58-64006A7986D3 */
DEFINE_GUID(SERVICE_GUID,
0x3049197c, 0xfacb, 0x11e6, 0xbd, 0x58, 0x64, 0x00, 0x6a, 0x79, 0x86, 0xd3);
#define SERVICE_PORT 0x3049197c
#define MY_BUFLEN 4096
#ifdef _MSC_VER
static WSADATA wsaData;
#endif
/* Use the vsock interface on Linux */
static int opt_vsock;
/* Handle a connection. Echo back anything sent to us and when the
* connection is closed send a bye message.
*/
static void handle(SOCKET fd)
{
char recvbuf[MY_BUFLEN];
int recvbuflen = MY_BUFLEN;
const char *byebuf = "Bye!";
int sent;
int res;
do {
res = recv(fd, recvbuf, recvbuflen, 0);
if (res == 0) {
printf("Peer closed\n");
break;
} else if (res == SOCKET_ERROR) {
sockerr("recv()");
return;
}
/* No error, echo */
printf("Bytes received: %d\n", res);
sent = send(fd, recvbuf, res, 0);
if (sent == SOCKET_ERROR) {
sockerr("send()");
return;
}
printf("Bytes sent: %d\n", sent);
} while (res > 0);
/* Send bye */
sent = send(fd, byebuf, sizeof(byebuf), 0);
if (sent == SOCKET_ERROR) {
sockerr("send() bye");
return;
}
printf("Bye Bytes sent: %d\n", sent);
}
/* Server:
* accept() in an endless loop, handle a connection at a time
*/
static int server(void)
{
SOCKET lsock, csock;
SOCKADDR_VM savm, sacvm;
SOCKADDR_HV sahv, sachv;
socklen_t socklen;
int res;
if (opt_vsock)
lsock = socket(AF_VSOCK, SOCK_STREAM, 0);
else
lsock = socket(AF_HYPERV, SOCK_STREAM, HV_PROTOCOL_RAW);
if (lsock == INVALID_SOCKET) {
sockerr("socket()");
return 1;
}
memset(&savm, 0, sizeof(savm));
savm.Family = AF_VSOCK;
savm.SvmCID = VMADDR_CID_ANY;
savm.SvmPort = SERVICE_PORT;
memset(&sahv, 0, sizeof(sahv));
sahv.Family = AF_HYPERV;
sahv.VmId = HV_GUID_WILDCARD;
sahv.ServiceId = SERVICE_GUID;
if (opt_vsock)
res = bind(lsock, (const struct sockaddr *)&savm, sizeof(savm));
else
res = bind(lsock, (const struct sockaddr *)&sahv, sizeof(sahv));
if (res == SOCKET_ERROR) {
sockerr("bind()");
closesocket(lsock);
return 1;
}
res = listen(lsock, SOMAXCONN);
if (res == SOCKET_ERROR) {
sockerr("listen()");
closesocket(lsock);
return 1;
}
while(1) {
memset(&sacvm, 0, sizeof(sacvm));
memset(&sachv, 0, sizeof(sachv));
if (opt_vsock) {
socklen = sizeof(sacvm);
csock = accept(lsock, (struct sockaddr *)&sacvm, &socklen);
} else {
socklen = sizeof(sachv);
csock = accept(lsock, (struct sockaddr *)&sachv, &socklen);
}
if (csock == INVALID_SOCKET) {
sockerr("accept()");
closesocket(lsock);
return 1;
}
if (opt_vsock)
printf("Connect from: 0x%08x.0x%08x\n", sacvm.SvmCID, sacvm.SvmPort);
else
printf("Connect from: "GUID_FMT":"GUID_FMT"\n",
GUID_ARGS(sachv.VmId), GUID_ARGS(sachv.ServiceId));
handle(csock);
closesocket(csock);
}
}
/* The client sends a messages, and waits for the echo before shutting
* down the send side. It then expects a bye message from the server.
*/
static int client(GUID target_guid, unsigned int target_cid)
{
SOCKET fd = INVALID_SOCKET;
SOCKADDR_VM savm;
SOCKADDR_HV sahv;
char *sendbuf = "this is a test";
char recvbuf[MY_BUFLEN];
int recvbuflen = MY_BUFLEN;
int res;
if (opt_vsock)
fd = socket(AF_VSOCK, SOCK_STREAM, 0);
else
fd = socket(AF_HYPERV, SOCK_STREAM, HV_PROTOCOL_RAW);
if (fd == INVALID_SOCKET) {
sockerr("socket()");
return 1;
}
memset(&sahv, 0, sizeof(sahv));
savm.Family = AF_VSOCK;
savm.SvmCID = target_cid;
savm.SvmPort = SERVICE_PORT;
memset(&sahv, 0, sizeof(sahv));
sahv.Family = AF_HYPERV;
sahv.VmId = target_guid;
sahv.ServiceId = SERVICE_GUID;
if (opt_vsock) {
printf("Connect to: 0x%08x.0x%08x\n", savm.SvmCID, savm.SvmPort);
res = connect(fd, (const struct sockaddr *)&savm, sizeof(savm));
} else {
printf("Connect to: "GUID_FMT":"GUID_FMT"\n",
GUID_ARGS(sahv.VmId), GUID_ARGS(sahv.ServiceId));
res = connect(fd, (const struct sockaddr *)&sahv, sizeof(sahv));
}
if (res == SOCKET_ERROR) {
sockerr("connect()");
goto out;
}
res = send(fd, sendbuf, (int)strlen(sendbuf), 0);
if (res == SOCKET_ERROR) {
sockerr("send()");
goto out;
}
printf("Bytes Sent: %d\n", res);
res = recv(fd, recvbuf, recvbuflen, 0);
if (res < 0) {
sockerr("recv()");
goto out;
} else if (res == 0) {
printf("Connection closed\n");
res = 1;
goto out;
}
printf("Bytes received: %d\n", res);
printf("->%s\n", recvbuf);
printf("Shutdown\n");
/* XXX shutdown does not work! */
res = shutdown(fd, SD_SEND);
if (res == SOCKET_ERROR) {
sockerr("shutdown()");
goto out;
}
printf("Wait for bye\n");
res = recv(fd, recvbuf, recvbuflen, 0);
if (res < 0) {
sockerr("recv()");
goto out;
} else if (res == 0) {
printf("Connection closed\n");
res = 1;
goto out;
}
printf("Bytes received: %d\n", res);
recvbuf[res] = '\0';
printf("->%s\n", recvbuf);
res = 0;
out:
closesocket(fd);
return res;
}
void usage(char *name)
{
printf("%s: -s | -c <carg> [-vsock]\n", name);
printf(" -s Server mode\n");
printf(" -c <carg> Client mode. <carg>:\n");
printf(" 'loopback': Connect in loopback mode\n");
printf(" 'parent': Connect to the parent partition\n");
printf(" <guid>: Connect to VM with GUID\n");
printf(" -vsock Use AF_VSOCK (Linux only)\n");
}
int __cdecl main(int argc, char **argv)
{
int opt_server;
int res = 0;
GUID target_guid;
unsigned int target_cid;
char *target_str = NULL;
int i;
#ifdef _MSC_VER
// Initialize Winsock
res = WSAStartup(MAKEWORD(2,2), &wsaData);
if (res != 0) {
fprintf(stderr, "WSAStartup() failed with error: %d\n", res);
return 1;
}
#endif
/* No getopt on windows. Do some manual parsing */
for (i = 1; i < argc; i++) {
if (strcmp(argv[i], "-s") == 0) {
opt_server = 1;
} else if (strcmp(argv[i], "-c") == 0) {
opt_server = 0;
if (i + 1 >= argc) {
fprintf(stderr, "-c requires an argument\n");
usage(argv[0]);
goto out;
}
if (strcmp(argv[i + 1], "loopback") == 0) {
target_guid = HV_GUID_LOOPBACK;
target_cid = VMADDR_CID_HYPERVISOR;
} else if (strcmp(argv[i + 1], "parent") == 0) {
target_guid = HV_GUID_PARENT;
target_cid = VMADDR_CID_HOST;
} else {
target_str = argv[i + 1];
}
i++;
} else if (strcmp(argv[i], "-vsock") == 0) {
opt_vsock = 1;
} else {
fprintf(stderr, "Unknown argument: %s\n", argv[i]);
usage(argv[0]);
goto out;
}
}
#ifdef _MSC_VER
if (opt_vsock) {
fprintf(stderr, "-vsock is not valid on Windows\n");
goto out;
}
#endif
if (target_str) {
if (opt_vsock) {
target_cid = strtoul(target_str, NULL, 0);
} else {
res = parseguid(target_str, &target_guid);
if (res != 0) {
fprintf(stderr, "failed to scan: %s\n", target_str);
goto out;
}
}
}
if (opt_server)
res = server();
else
res = client(target_guid, target_cid);
out:
#ifdef _MSC_VER
WSACleanup();
#endif
return res;
}

View File

@ -0,0 +1,607 @@
/*
* A simple Hyper-V sockets stress test.
*
* This program uses a configurable number of client threads which all
* open a connection to a server and then transfer a random amount of
* data to the server which echos the data back.
*
* The send()/recv() calls optionally alternate between RXTX_BUF_LEN
* and RXTX_SMALL_LEN worth of data to add more variability in the
* interaction.
*/
#include "compat.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* 3049197C-FACB-11E6-BD58-64006A7986D3 */
DEFINE_GUID(SERVICE_GUID,
0x3049197c, 0xfacb, 0x11e6, 0xbd, 0x58, 0x64, 0x00, 0x6a, 0x79, 0x86, 0xd3);
#define SERVICE_PORT 0x3049197c
/* Maximum amount of data for a single send()/recv() call.
* Note: On Windows the maximum length seems to be 8KB and if larger
* buffers are passed to send() the connection will be close. We could
* use getsockopt(SO_MAX_MSG_SIZE) */
#define RXTX_BUF_LEN (4 * 1024)
/* Small send()/recv() lengths */
#define RXTX_SMALL_LEN 4
/* Default number of connections made by the client */
#define DEFAULT_CLIENT_CONN 100
/* Maximum amount of data to send per connection */
static int opt_max_len = 20 * 1024 * 1024;
/* Global flag to alternate between short and long send()/recv() buffers */
static int opt_alternate;
/* Use the vsock interface on Linux */
static int opt_vsock;
static int verbose;
#define INFO(...) \
do { \
if (verbose) { \
printf(__VA_ARGS__); \
fflush(stdout); \
} \
} while (0)
#define DBG(...) \
do { \
if (verbose > 1) { \
printf(__VA_ARGS__); \
fflush(stdout); \
} \
} while (0)
#define TRC(...) \
do { \
if (verbose > 2) { \
printf(__VA_ARGS__); \
fflush(stdout); \
} \
} while (0)
#ifdef _MSC_VER
static WSADATA wsaData;
#endif
static unsigned char sendbuf[RXTX_BUF_LEN];
/* A simple hexdump */
static void dump(int id, int conn, const unsigned char *b, int len)
{
int i, c;
for (i = 0; i < (len + 16 - 1 - (len - 1) % 16); i += 16) {
printf("[%02d:%05d] %04x: ", id, conn, i);
for (c = i; c < i + 8; c++)
if ( c < len)
printf("%02x ", b[c]);
printf(" ");
for (c = i + 8; c < i + 16; c++)
if ( c < len)
printf("%02x ", b[c]);
printf("\n");
}
fflush(stdout);
}
/* Server code
*
* The server accepts a new connection and spins of a new thread to
* handle it. The thread simply echos back the data. We use a thread
* per connection because it's simpler code, but ideally we should be
* using a pool of worker threads.
*/
/* Arguments to the server thread */
struct svr_args {
SOCKET fd;
int conn;
};
/* Thread entry point for a server */
static void *handle(void *a)
{
struct svr_args *args = a;
uint64_t start, end, diff;
char recvbuf[RXTX_BUF_LEN];
int total_bytes = 0;
int rxlen = RXTX_SMALL_LEN;
int received;
int sent;
int res;
TRC("[%05d] server: handle fd=%d\n", args->conn, (int)args->fd);
start = time_ns();
for (;;) {
if (opt_alternate)
rxlen = (rxlen == RXTX_SMALL_LEN) ? RXTX_BUF_LEN : RXTX_SMALL_LEN;
else
rxlen = RXTX_BUF_LEN;
received = recv(args->fd, recvbuf, rxlen, 0);
if (received == 0) {
DBG("[%05d] Peer closed\n", args->conn);
break;
} else if (received == SOCKET_ERROR) {
sockerr("recv()");
goto out;
}
TRC("[%05d] server: fd=%d RX %d bytes\n",
args->conn, (int)args->fd, received);
sent = 0;
while (sent < received) {
res = send(args->fd, recvbuf + sent, received - sent, 0);
if (res == SOCKET_ERROR) {
sockerr("send()");
goto out;
}
sent += res;
TRC("[%05d] server: fd=%d TX %d bytes\n",
args->conn, (int)args->fd, res);
}
total_bytes += sent;
}
end = time_ns();
out:
diff = end - start;
diff /= 1000 * 1000;
INFO("[%05d] ECHOED: %9d Bytes in %5"PRIu64"ms\n",
args->conn, total_bytes, diff);
TRC("close(%d)\n", (int)args->fd);
closesocket(args->fd);
free(args);
return NULL;
}
/* Server entry point */
static int server(int multi_threaded, int max_conn)
{
SOCKET lsock, csock;
SOCKADDR_VM savm, sacvm;
SOCKADDR_HV sahv, sachv;
socklen_t socklen;
struct svr_args *args;
THREAD_HANDLE st;
int conn = 0;
int res;
if (opt_vsock)
lsock = socket(AF_VSOCK, SOCK_STREAM, 0);
else
lsock = socket(AF_HYPERV, SOCK_STREAM, HV_PROTOCOL_RAW);
if (lsock == INVALID_SOCKET) {
sockerr("socket()");
return 1;
}
memset(&savm, 0, sizeof(savm));
savm.Family = AF_VSOCK;
savm.SvmCID = VMADDR_CID_ANY;
savm.SvmPort = SERVICE_PORT;
memset(&sahv, 0, sizeof(sahv));
sahv.Family = AF_HYPERV;
sahv.VmId = HV_GUID_WILDCARD;
sahv.ServiceId = SERVICE_GUID;
if (opt_vsock)
res = bind(lsock, (const struct sockaddr *)&savm, sizeof(savm));
else
res = bind(lsock, (const struct sockaddr *)&sahv, sizeof(sahv));
if (res == SOCKET_ERROR) {
sockerr("bind()");
closesocket(lsock);
return 1;
}
res = listen(lsock, SOMAXCONN);
if (res == SOCKET_ERROR) {
sockerr("listen()");
closesocket(lsock);
return 1;
}
while(1) {
memset(&sacvm, 0, sizeof(sacvm));
memset(&sachv, 0, sizeof(sachv));
if (opt_vsock) {
socklen = sizeof(sacvm);
csock = accept(lsock, (struct sockaddr *)&sacvm, &socklen);
} else {
socklen = sizeof(sachv);
csock = accept(lsock, (struct sockaddr *)&sachv, &socklen);
}
if (csock == INVALID_SOCKET) {
sockerr("accept()");
closesocket(lsock);
return 1;
}
if (opt_vsock)
DBG("Connect from: 0x%08x.0x%08x\n", sacvm.SvmCID, sacvm.SvmPort);
else
DBG("Connect from: "GUID_FMT":"GUID_FMT"\n",
GUID_ARGS(sachv.VmId), GUID_ARGS(sachv.ServiceId));
/* Spin up a new thread per connection. Not the most
* efficient, but stops us from having to faff about with
* worker threads and the like. */
args = malloc(sizeof(*args));
if (!args) {
fprintf(stderr, "failed to malloc thread state\n");
return 1;
}
args->fd = csock;
args->conn = conn++;
if (multi_threaded) {
thread_create(&st, &handle, args);
thread_detach(st);
} else {
handle(args);
}
/* Note, since we are not waiting for thread to finish, this may
* cause the last n connections not being handled properly. */
if (conn >= max_conn)
break;
}
closesocket(lsock);
return 0;
}
/* Client code
*
* The client sends one message of random size and expects the server
* to echo it back. The sending is done in a separate thread so we can
* simultaneously drain the server's replies. Could do this in a
* single thread with poll()/select() as well, but this keeps the code
* simpler.
*/
/* Arguments for client threads */
struct client_args {
THREAD_HANDLE h;
GUID target_guid;
unsigned int target_cid;
int id;
int conns;
int rand;
int res;
};
/* Argument passed to Client send thread */
struct client_tx_args {
SOCKET fd;
int tosend;
int id;
int conn;
};
static void *client_tx(void *a)
{
struct client_tx_args *args = a;
char tmp[128];
int tosend, txlen = RXTX_SMALL_LEN;
int res;
tosend = args->tosend;
while (tosend) {
if (opt_alternate)
txlen = (txlen == RXTX_SMALL_LEN) ? RXTX_BUF_LEN : RXTX_SMALL_LEN;
else
txlen = RXTX_BUF_LEN;
txlen = (tosend > txlen) ? txlen : tosend;
res = send(args->fd, sendbuf, txlen, 0);
if (res == SOCKET_ERROR) {
snprintf(tmp, sizeof(tmp), "[%02d:%05d] send() after %d bytes",
args->id, args->conn, args->tosend - tosend);
sockerr(tmp);
goto out;
}
TRC("[%02d:%05d] client: TX %d bytes\n", args->id, args->conn, res);
tosend -= res;
}
DBG("[%02d:%05d] TX: %9d bytes sent\n", args->id, args->conn, args->tosend);
out:
return NULL;
}
/* Client code for a single connection */
static int client_one(GUID target_guid, unsigned int target_cid,
int id, int conn)
{
struct client_tx_args args;
uint64_t start, end, diff;
THREAD_HANDLE st;
SOCKADDR_VM savm;
SOCKADDR_HV sahv;
SOCKET fd;
unsigned char recvbuf[RXTX_BUF_LEN];
int rxlen = RXTX_SMALL_LEN;
char tmp[128];
int tosend, received = 0;
int res;
TRC("[%02d:%05d] start\n", id, conn);
start = time_ns();
if (opt_vsock)
fd = socket(AF_VSOCK, SOCK_STREAM, 0);
else
fd = socket(AF_HYPERV, SOCK_STREAM, HV_PROTOCOL_RAW);
if (fd == INVALID_SOCKET) {
sockerr("socket()");
return 1;
}
if (opt_vsock) {
savm.Family = AF_VSOCK;
savm.SvmCID = target_cid;
savm.SvmPort = SERVICE_PORT;
DBG("[%02d:%05d] Connected to: 0x%08x.0x%08x fd=%d\n",
id, conn, savm.SvmCID, savm.SvmPort, (int)fd);
res = connect(fd, (const struct sockaddr *)&savm, sizeof(savm));
} else {
sahv.Family = AF_HYPERV;
sahv.Reserved = 0;
sahv.VmId = target_guid;
sahv.ServiceId = SERVICE_GUID;
DBG("[%02d:%05d] Connected to: "GUID_FMT":"GUID_FMT" fd=%d\n",
id, conn, GUID_ARGS(sahv.VmId), GUID_ARGS(sahv.ServiceId), (int)fd);
res = connect(fd, (const struct sockaddr *)&sahv, sizeof(sahv));
}
if (res == SOCKET_ERROR) {
sockerr("connect()");
goto out;
}
if (RAND_MAX < opt_max_len)
tosend = (int)((1ULL * RAND_MAX + 1) * rand() + rand());
else
tosend = rand();
tosend = tosend % (opt_max_len - 1) + 1;
DBG("[%02d:%05d] TOSEND: %d bytes\n", id, conn, tosend);
args.fd = fd;
args.tosend = tosend;
args.id = id;
args.conn = conn;
thread_create(&st, &client_tx, &args);
while (received < tosend) {
if (opt_alternate)
rxlen = (rxlen == RXTX_SMALL_LEN) ? RXTX_BUF_LEN : RXTX_SMALL_LEN;
else
rxlen = RXTX_BUF_LEN;
res = recv(fd, recvbuf, rxlen, 0);
if (res < 0) {
snprintf(tmp, sizeof(tmp), "[%02d:%05d] recv() after %d bytes",
id, conn, received);
sockerr(tmp);
goto thout;
} else if (res == 0) {
INFO("[%02d:%05d] Connection closed\n", id, conn);
res = 1;
goto thout;
}
TRC("[%02d:%05d] client: RX %d bytes\n", id, conn, res);
if (verbose > 3)
dump(id, conn, recvbuf, res);
received += res;
}
res = 0;
thout:
thread_join(st);
end = time_ns();
diff = end - start;
diff /= 1000 * 1000;
INFO("[%02d:%05d] TX/RX: %9d bytes in %5"PRIu64"ms\n",
id, conn, received, diff);
out:
TRC("[%02d:%05d] close(%d)\n", id, conn, (int)fd);
closesocket(fd);
return res;
}
static void *client_thd(void *a)
{
struct client_args *args = a;
int res, i;
if (args->rand)
srand(time(NULL) + args->id);
for (i = 0; i < args->conns; i++) {
res = client_one(args->target_guid, args->target_cid, args->id, i);
if (res)
break;
}
args->res = res;
return args;
}
void usage(char *name)
{
printf("%s: -s|-c <carg> [-i <conns>]\n", name);
printf(" -s Server mode\n");
printf(" -1 "
"Use a single thread (handle one connection at a time)\n");
printf("\n");
printf(" -c <carg> Client mode. <carg>:\n");
printf(" 'loopback': Connect in loopback mode\n");
printf(" 'parent': Connect to the parent partition\n");
printf(" <guid>: Connect to VM with GUID\n");
printf(" -p <num> Run 'num' connections in parallel (default 1)\n");
printf(" -m <num> Maximum amount of data to send per connection\n");
printf(" -r Initialise random number generator with the time\n");
printf("\n");
printf("Common options\n");
printf(" -i <conns> Number connections the client makes (default %d)\n",
DEFAULT_CLIENT_CONN);
printf(" -vsock Use vsock (Linux only)\n");
printf(" -a Alternate using short/long send()/recv() buffers\n");
printf(" -v Verbose output (use multiple times)\n");
}
int __cdecl main(int argc, char **argv)
{
struct client_args *args;
int opt_conns = DEFAULT_CLIENT_CONN;
int opt_multi_thds = 1;
int opt_server = 0;
int opt_rand = 0;
int opt_par = 1;
GUID target_guid;
unsigned int target_cid;
char *target_str = NULL;
int res = 0;
int i;
#ifdef _MSC_VER
/* Initialize Winsock */
res = WSAStartup(MAKEWORD(2,2), &wsaData);
if (res != 0) {
fprintf(stderr, "WSAStartup() failed with error: %d\n", res);
return 1;
}
#endif
/* No getopt on windows. Do some manual parsing */
for (i = 1; i < argc; i++) {
if (strcmp(argv[i], "-s") == 0) {
opt_server = 1;
} else if (strcmp(argv[i], "-c") == 0) {
opt_server = 0;
if (i + 1 >= argc) {
fprintf(stderr, "-c requires an argument\n");
usage(argv[0]);
goto out;
}
if (strcmp(argv[i + 1], "loopback") == 0) {
target_guid = HV_GUID_LOOPBACK;
target_cid = VMADDR_CID_HYPERVISOR;
} else if (strcmp(argv[i + 1], "parent") == 0) {
target_guid = HV_GUID_PARENT;
target_cid = VMADDR_CID_HOST;
} else {
target_str = argv[i + 1];
}
i++;
} else if (strcmp(argv[i], "-i") == 0) {
if (i + 1 >= argc) {
fprintf(stderr, "-i requires an argument\n");
usage(argv[0]);
goto out;
}
opt_conns = atoi(argv[++i]);
} else if (strcmp(argv[i], "-p") == 0) {
if (i + 1 >= argc) {
fprintf(stderr, "-p requires an argument\n");
usage(argv[0]);
goto out;
}
opt_par = atoi(argv[++i]);
} else if (strcmp(argv[i], "-m") == 0) {
if (i + 1 >= argc) {
fprintf(stderr, "-p requires an argument\n");
usage(argv[0]);
goto out;
}
opt_max_len = atoi(argv[++i]);
} else if (strcmp(argv[i], "-r") == 0) {
opt_rand = 1;
} else if (strcmp(argv[i], "-1") == 0) {
opt_multi_thds = 0;
} else if (strcmp(argv[i], "-vsock") == 0) {
opt_vsock = 1;
} else if (strcmp(argv[i], "-a") == 0) {
opt_alternate = 1;
} else if (strcmp(argv[i], "-v") == 0) {
verbose++;
} else {
usage(argv[0]);
goto out;
}
}
#ifdef _MSC_VER
if (opt_vsock) {
fprintf(stderr, "-vsock is not valid on Windows\n");
goto out;
}
#endif
if (target_str) {
if (opt_vsock) {
target_cid = strtoul(target_str, NULL, 0);
} else {
res = parseguid(target_str, &target_guid);
if (res != 0) {
fprintf(stderr, "failed to scan: %s\n", target_str);
goto out;
}
}
}
if (opt_server) {
server(opt_multi_thds, opt_conns);
} else {
/* Initialise the send buffer with a known pattern */
for (i = 0; i < RXTX_BUF_LEN; i++) {
if ((i >> 8) % 2)
sendbuf[i] = i & 0xff;
else
sendbuf[i] = 0xff - (i & 0xff);
}
args = calloc(opt_par, sizeof(*args));
if (!args) {
fprintf(stderr, "failed to malloc");
res = -1;
goto out;
}
/* Create threads */
for (i = 0; i < opt_par; i++) {
args[i].target_guid = target_guid;
args[i].target_cid = target_cid;
args[i].id = i;
args[i].conns = opt_conns / opt_par;
args[i].rand = opt_rand;
thread_create(&args[i].h, &client_thd, &args[i]);
}
/* Wait for threads to finish and collect return codes */
res = 0;
for (i = 0; i < opt_par; i++) {
thread_join(args[i].h);
if (args[i].res)
fprintf(stderr, "THREAD[%d] failed with %d\n", i, args[i].res);
res |= args[i].res;
}
}
out:
#ifdef _MSC_VER
WSACleanup();
#endif
return res;
}

View File

@ -0,0 +1,115 @@
// Package hvsock provides a Go interface to Hyper-V sockets both on
// Windows and on Linux. The Linux bindings require patches for the
// 4.9.x kernel. If you are using a Linux kernel 4.14.x or newer you
// should use the vsock package instead as the Hyper-V socket support
// in these kernels have been merged with the virtio sockets
// implementation.
package hvsock
import (
"encoding/binary"
"fmt"
"net"
"reflect"
)
var (
// GUIDZero used by listeners to accept connections from all partitions
GUIDZero, _ = GUIDFromString("00000000-0000-0000-0000-000000000000")
// GUIDWildcard used by listeners to accept connections from all partitions
GUIDWildcard, _ = GUIDFromString("00000000-0000-0000-0000-000000000000")
// GUIDBroadcast undocumented
GUIDBroadcast, _ = GUIDFromString("FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF")
// GUIDChildren used by listeners to accept connections from children
GUIDChildren, _ = GUIDFromString("90db8b89-0d35-4f79-8ce9-49ea0ac8b7cd")
// GUIDLoopback use to connect in loopback mode
GUIDLoopback, _ = GUIDFromString("e0e16197-dd56-4a10-9195-5ee7a155a838")
// GUIDParent use to connect to the parent partition
GUIDParent, _ = GUIDFromString("a42e7cda-d03f-480c-9cc2-a4de20abb878")
// GUIDs for LinuxVMs with the new Hyper-V socket implementation need to match this template
guidTemplate, _ = GUIDFromString("00000000-facb-11e6-bd58-64006a7986d3")
)
const (
// The Hyper-V socket implementation used in the 4.9.x kernels
// seems to fail silently if messages are above 8k. The newer
// implementation in the 4.14.x (and newer) kernels seems to
// work fine with larger messages. This is constant is used as
// a temporary workaround to limit the amount of data sent and
// should be removed once support for 4.9.x kernels is
// deprecated.
maxMsgSize = 8 * 1024
)
// GUID is used by Hypper-V sockets for "addresses" and "ports"
type GUID [16]byte
// Convert a GUID into a string
func (g *GUID) String() string {
/* XXX This assume little endian */
return fmt.Sprintf("%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
g[3], g[2], g[1], g[0],
g[5], g[4],
g[7], g[6],
g[8], g[9],
g[10], g[11], g[12], g[13], g[14], g[15])
}
// Port converts a Service GUID to a "port" usable by the vsock package.
// It can be used to convert hvsock code to vsock code. On 4.14.x
// kernels Service GUIDs for talking to Linux should have the form of
// xxxxxxxx-facb-11e6-bd58-64006a7986d3, where xxxxxxxx is the vsock port.
func (g *GUID) Port() (uint32, error) {
// Check that the GUID is as expected
if !reflect.DeepEqual(g[4:], guidTemplate[4:]) {
return 0, fmt.Errorf("%s does not conform with the template", g)
}
return binary.LittleEndian.Uint32(g[0:4]), nil
}
// GUIDFromString parses a string and returns a GUID
func GUIDFromString(s string) (GUID, error) {
var g GUID
var err error
_, err = fmt.Sscanf(s, "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
&g[3], &g[2], &g[1], &g[0],
&g[5], &g[4],
&g[7], &g[6],
&g[8], &g[9],
&g[10], &g[11], &g[12], &g[13], &g[14], &g[15])
return g, err
}
// Addr represents a Hyper-V socket address
type Addr struct {
VMID GUID
ServiceID GUID
}
// Network returns the type of network for Hyper-V sockets
func (a Addr) Network() string {
return "hvsock"
}
func (a Addr) String() string {
vmid := a.VMID.String()
svc := a.ServiceID.String()
return vmid + ":" + svc
}
// Conn is a hvsock connection which supports half-close.
type Conn interface {
net.Conn
CloseRead() error
CloseWrite() error
}
// Since there doesn't seem to be a standard min function
func min(x, y int) int {
if x < y {
return x
}
return y
}

View File

@ -0,0 +1,22 @@
// +build !linux,!windows
package hvsock
import (
"fmt"
"net"
"runtime"
)
// Supported returns if hvsocks are supported on your platform
func Supported() bool {
return false
}
func Dial(raddr Addr) (Conn, error) {
return nil, fmt.Errorf("Dial() not implemented on %s", runtime.GOOS)
}
func Listen(addr Addr) (net.Listener, error) {
return nil, fmt.Errorf("Listen() not implemented on %s", runtime.GOOS)
}

View File

@ -0,0 +1,262 @@
package hvsock
// On Linux we have to deal with two different implementations. The
// "legacy" implementation never made it into the kernel, but several
// kernels, including the LinuxKit one carried patches for it for
// quite a while. The legacy version defined a new address family
// while the new version sits on top of the existing VMware/virtio
// socket implementation.
//
// We try to determine at init if we are on a kernel with the legacy
// implementation or the new version and set "legacyMode" accordingly.
//
// We can't just reuse the vsock implementation as we still need to
// emulated CloseRead()/CloseWrite() as not all Windows builds support
// it.
/*
#include <sys/socket.h>
struct sockaddr_hv {
unsigned short shv_family;
unsigned short reserved;
unsigned char shv_vm_id[16];
unsigned char shv_service_id[16];
};
int bind_sockaddr_hv(int fd, const struct sockaddr_hv *sa_hv) {
return bind(fd, (const struct sockaddr*)sa_hv, sizeof(*sa_hv));
}
int connect_sockaddr_hv(int fd, const struct sockaddr_hv *sa_hv) {
return connect(fd, (const struct sockaddr*)sa_hv, sizeof(*sa_hv));
}
int accept_hv(int fd, struct sockaddr_hv *sa_hv, socklen_t *sa_hv_len) {
return accept4(fd, (struct sockaddr *)sa_hv, sa_hv_len, 0);
}
*/
import "C"
import (
"fmt"
"net"
"os"
"syscall"
"time"
"github.com/pkg/errors"
"golang.org/x/sys/unix"
)
const (
hvsockAF = 43 //SHV_PROTO_RAW
hvsockRaw = 1 // SHV_PROTO_RAW
)
// Supported returns if hvsocks are supported on your platform
func Supported() bool {
// Try opening a hvsockAF socket. If it works we are on older, i.e. 4.9.x kernels.
// 4.11 defines AF_SMC as 43 but it doesn't support protocol 1 so the
// socket() call should fail.
fd, err := syscall.Socket(hvsockAF, syscall.SOCK_STREAM, hvsockRaw)
if err != nil {
return false
}
syscall.Close(fd)
return true
}
// Dial a Hyper-V socket address
func Dial(raddr Addr) (Conn, error) {
fd, err := syscall.Socket(hvsockAF, syscall.SOCK_STREAM, hvsockRaw)
if err != nil {
return nil, err
}
sa := C.struct_sockaddr_hv{}
sa.shv_family = hvsockAF
sa.reserved = 0
for i := 0; i < 16; i++ {
sa.shv_vm_id[i] = C.uchar(raddr.VMID[i])
}
for i := 0; i < 16; i++ {
sa.shv_service_id[i] = C.uchar(raddr.ServiceID[i])
}
// Retry connect in a loop if EINTR is encountered.
for {
if ret, errno := C.connect_sockaddr_hv(C.int(fd), &sa); ret != 0 {
if errno == syscall.EINTR {
continue
}
return nil, fmt.Errorf("connect(%s) failed with %d, errno=%d", raddr, ret, errno)
}
break
}
return newHVsockConn(uintptr(fd), &Addr{VMID: GUIDZero, ServiceID: GUIDZero}, &raddr), nil
}
// Listen returns a net.Listener which can accept connections on the given port
func Listen(addr Addr) (net.Listener, error) {
fd, err := syscall.Socket(hvsockAF, syscall.SOCK_STREAM, hvsockRaw)
if err != nil {
return nil, err
}
sa := C.struct_sockaddr_hv{}
sa.shv_family = hvsockAF
sa.reserved = 0
for i := 0; i < 16; i++ {
sa.shv_vm_id[i] = C.uchar(addr.VMID[i])
}
for i := 0; i < 16; i++ {
sa.shv_service_id[i] = C.uchar(addr.ServiceID[i])
}
if ret, errno := C.bind_sockaddr_hv(C.int(fd), &sa); ret != 0 {
return nil, fmt.Errorf("listen(%s) failed with %d, errno=%d", addr, ret, errno)
}
err = syscall.Listen(fd, syscall.SOMAXCONN)
if err != nil {
return nil, errors.Wrapf(err, "listen(%s) failed", addr)
}
return &hvsockListener{fd, addr}, nil
}
//
// Hyper-v sockets Listener implementation
//
type hvsockListener struct {
fd int
local Addr
}
// Accept accepts an incoming call and returns the new connection.
func (v *hvsockListener) Accept() (net.Conn, error) {
var acceptSA C.struct_sockaddr_hv
var acceptSALen C.socklen_t
acceptSALen = C.sizeof_struct_sockaddr_hv
fd, err := C.accept_hv(C.int(v.fd), &acceptSA, &acceptSALen)
if err != nil {
return nil, errors.Wrapf(err, "accept(%s) failed", v.local)
}
remote := &Addr{VMID: guidFromC(acceptSA.shv_vm_id), ServiceID: guidFromC(acceptSA.shv_service_id)}
return newHVsockConn(uintptr(fd), &v.local, remote), nil
}
// Close closes the listening connection
func (v *hvsockListener) Close() error {
// Note this won't cause the Accept to unblock.
return unix.Close(v.fd)
}
// Addr returns the address the Listener is listening on
func (v *hvsockListener) Addr() net.Addr {
return v.local
}
//
// Hyper-V socket connection implementation
//
// hvsockConn represents a connection over a Hyper-V socket
type hvsockConn struct {
hvsock *os.File
fd uintptr
local *Addr
remote *Addr
}
func newHVsockConn(fd uintptr, local, remote *Addr) *hvsockConn {
hvsock := os.NewFile(fd, fmt.Sprintf("hvsock:%d", fd))
return &hvsockConn{hvsock: hvsock, fd: fd, local: local, remote: remote}
}
// LocalAddr returns the local address of a connection
func (v *hvsockConn) LocalAddr() net.Addr {
return v.local
}
// RemoteAddr returns the remote address of a connection
func (v *hvsockConn) RemoteAddr() net.Addr {
return v.remote
}
// Close closes the connection
func (v *hvsockConn) Close() error {
return v.hvsock.Close()
}
// CloseRead shuts down the reading side of a hvsock connection
func (v *hvsockConn) CloseRead() error {
return syscall.Shutdown(int(v.fd), syscall.SHUT_RD)
}
// CloseWrite shuts down the writing side of a hvsock connection
func (v *hvsockConn) CloseWrite() error {
return syscall.Shutdown(int(v.fd), syscall.SHUT_WR)
}
// Read reads data from the connection
func (v *hvsockConn) Read(buf []byte) (int, error) {
return v.hvsock.Read(buf)
}
// Write writes data over the connection
// TODO(rn): replace with a straight call to v.hvsock.Write() once 4.9.x support is deprecated
func (v *hvsockConn) Write(buf []byte) (int, error) {
written := 0
toWrite := len(buf)
for toWrite > 0 {
thisBatch := min(toWrite, maxMsgSize)
n, err := v.hvsock.Write(buf[written : written+thisBatch])
if err != nil {
return written, err
}
if n != thisBatch {
return written, fmt.Errorf("short write %d != %d", n, thisBatch)
}
toWrite -= n
written += n
}
return written, nil
}
// SetDeadline sets the read and write deadlines associated with the connection
func (v *hvsockConn) SetDeadline(t time.Time) error {
return nil // FIXME
}
// SetReadDeadline sets the deadline for future Read calls.
func (v *hvsockConn) SetReadDeadline(t time.Time) error {
return nil // FIXME
}
// SetWriteDeadline sets the deadline for future Write calls
func (v *hvsockConn) SetWriteDeadline(t time.Time) error {
return nil // FIXME
}
// File duplicates the underlying socket descriptor and returns it.
func (v *hvsockConn) File() (*os.File, error) {
// This is equivalent to dup(2) but creates the new fd with CLOEXEC already set.
r0, _, e1 := syscall.Syscall(syscall.SYS_FCNTL, uintptr(v.hvsock.Fd()), syscall.F_DUPFD_CLOEXEC, 0)
if e1 != 0 {
return nil, os.NewSyscallError("fcntl", e1)
}
return os.NewFile(r0, v.hvsock.Name()), nil
}
func guidFromC(cg [16]C.uchar) GUID {
var g GUID
for i := 0; i < 16; i++ {
g[i] = byte(cg[i])
}
return g
}

View File

@ -0,0 +1,496 @@
package hvsock
import (
"fmt"
"io"
"log"
"net"
"runtime"
"sync"
"sync/atomic"
"syscall"
"time"
"unsafe"
"github.com/pkg/errors"
)
// Make sure Winsock2 is initialised
func init() {
e := syscall.WSAStartup(uint32(0x202), &wsaData)
if e != nil {
log.Fatal("WSAStartup", e)
}
}
const (
hvsockAF = 34 // AF_HYPERV
hvsockRaw = 1 // SHV_PROTO_RAW
)
var (
// ErrTimeout is an error returned on timeout
ErrTimeout = &timeoutError{}
wsaData syscall.WSAData
)
// Supported returns if hvsocks are supported on your platform
func Supported() bool {
return true
}
// Dial a Hyper-V socket address
func Dial(raddr Addr) (Conn, error) {
fd, err := syscall.Socket(hvsockAF, syscall.SOCK_STREAM, hvsockRaw)
if err != nil {
return nil, err
}
var sa rawSockaddrHyperv
ptr, n, err := raddr.sockaddr(&sa)
if err != nil {
return nil, err
}
if err := sys_connect(fd, ptr, n); err != nil {
return nil, errors.Wrapf(err, "connect(%s) failed", raddr)
}
return newHVsockConn(fd, Addr{VMID: GUIDZero, ServiceID: GUIDZero}, raddr)
}
// Listen returns a net.Listener which can accept connections on the given port
func Listen(addr Addr) (net.Listener, error) {
fd, err := syscall.Socket(hvsockAF, syscall.SOCK_STREAM, hvsockRaw)
if err != nil {
return nil, err
}
var sa rawSockaddrHyperv
ptr, n, err := addr.sockaddr(&sa)
if err != nil {
return nil, err
}
if err := sys_bind(fd, ptr, n); err != nil {
return nil, fmt.Errorf("bind(%s) failed with %v", addr, err)
}
err = syscall.Listen(fd, syscall.SOMAXCONN)
if err != nil {
return nil, errors.Wrapf(err, "listen(%s) failed", addr)
}
return &hvsockListener{fd, addr}, nil
}
//
// Hyper-v sockets Listener implementation
//
type hvsockListener struct {
fd syscall.Handle
local Addr
}
// Accept accepts an incoming call and returns the new connection
func (v *hvsockListener) Accept() (net.Conn, error) {
var sa rawSockaddrHyperv
var n = int32(unsafe.Sizeof(sa))
fd, err := sys_accept(v.fd, &sa, &n)
if err != nil {
return nil, err
}
// Extract an Addr from sa
raddr := Addr{}
for i := 0; i < len(raddr.VMID); i++ {
raddr.VMID[i] = sa.VMID[i]
}
for i := 0; i < len(raddr.ServiceID); i++ {
raddr.ServiceID[i] = sa.ServiceID[i]
}
return newHVsockConn(fd, v.local, raddr)
}
// Close closes the listening connection
func (v *hvsockListener) Close() error {
return syscall.Close(v.fd)
}
// Addr returns the address the Listener is listening on
func (v *hvsockListener) Addr() net.Addr {
return v.local
}
//
// Hyper-V socket connection implementation
//
// hvsockConn represent a Hyper-V connection. Complex mostly due to asynch send()/recv() syscalls.
type hvsockConn struct {
fd syscall.Handle
local Addr
remote Addr
wg sync.WaitGroup
wgLock sync.RWMutex
closing atomicBool
readDeadline deadlineHandler
writeDeadline deadlineHandler
}
func newHVsockConn(h syscall.Handle, local Addr, remote Addr) (*hvsockConn, error) {
ioInitOnce.Do(initIo)
v := &hvsockConn{fd: h, local: local, remote: remote}
_, err := createIoCompletionPort(h, ioCompletionPort, 0, 0xffffffff)
if err != nil {
return nil, err
}
err = setFileCompletionNotificationModes(h,
cFILE_SKIP_COMPLETION_PORT_ON_SUCCESS|cFILE_SKIP_SET_EVENT_ON_HANDLE)
if err != nil {
return nil, err
}
v.readDeadline.channel = make(timeoutChan)
v.writeDeadline.channel = make(timeoutChan)
return v, nil
}
// LocalAddr returns the local address of a connection
func (v *hvsockConn) LocalAddr() net.Addr {
return v.local
}
// RemoteAddr returns the remote address of a connection
func (v *hvsockConn) RemoteAddr() net.Addr {
return v.remote
}
// Close closes the connection
func (v *hvsockConn) Close() error {
v.close()
return nil
}
// CloseRead shuts down the reading side of a hvsock connection
func (v *hvsockConn) CloseRead() error {
return syscall.Shutdown(v.fd, syscall.SHUT_RD)
}
// CloseWrite shuts down the writing side of a hvsock connection
func (v *hvsockConn) CloseWrite() error {
return syscall.Shutdown(v.fd, syscall.SHUT_WR)
}
// Read reads data from the connection
func (v *hvsockConn) Read(buf []byte) (int, error) {
var b syscall.WSABuf
var f uint32
b.Len = uint32(len(buf))
b.Buf = &buf[0]
c, err := v.prepareIo()
if err != nil {
return 0, err
}
defer v.wg.Done()
if v.readDeadline.timedout.isSet() {
return 0, ErrTimeout
}
var bytes uint32
err = syscall.WSARecv(v.fd, &b, 1, &bytes, &f, &c.o, nil)
n, err := v.asyncIo(c, &v.readDeadline, bytes, err)
runtime.KeepAlive(buf)
// Handle EOF conditions.
if err == nil && n == 0 && len(buf) != 0 {
return 0, io.EOF
} else if err == syscall.ERROR_BROKEN_PIPE {
return 0, io.EOF
} else {
return n, err
}
}
// Write writes data over the connection
// TODO(rn): Remove once 4.9.x support is deprecated
func (v *hvsockConn) Write(buf []byte) (int, error) {
written := 0
toWrite := len(buf)
for toWrite > 0 {
thisBatch := min(toWrite, maxMsgSize)
n, err := v.write(buf[written : written+thisBatch])
if err != nil {
return written, err
}
if n != thisBatch {
return written, fmt.Errorf("short write %d != %d", n, thisBatch)
}
toWrite -= n
written += n
}
return written, nil
}
func (v *hvsockConn) write(buf []byte) (int, error) {
var b syscall.WSABuf
var f uint32
if len(buf) == 0 {
return 0, nil
}
f = 0
b.Len = uint32(len(buf))
b.Buf = &buf[0]
c, err := v.prepareIo()
if err != nil {
return 0, err
}
defer v.wg.Done()
if v.writeDeadline.timedout.isSet() {
return 0, ErrTimeout
}
var bytes uint32
err = syscall.WSASend(v.fd, &b, 1, &bytes, f, &c.o, nil)
n, err := v.asyncIo(c, &v.writeDeadline, bytes, err)
runtime.KeepAlive(buf)
return n, err
}
// SetReadDeadline implementation for Hyper-V sockets
func (v *hvsockConn) SetReadDeadline(deadline time.Time) error {
return v.readDeadline.set(deadline)
}
// SetWriteDeadline implementation for Hyper-V sockets
func (v *hvsockConn) SetWriteDeadline(deadline time.Time) error {
return v.writeDeadline.set(deadline)
}
// SetDeadline implementation for Hyper-V sockets
func (v *hvsockConn) SetDeadline(deadline time.Time) error {
if err := v.SetReadDeadline(deadline); err != nil {
return err
}
return v.SetWriteDeadline(deadline)
}
// Helper functions for conversion to sockaddr
// struck sockaddr equivalent
type rawSockaddrHyperv struct {
Family uint16
Reserved uint16
VMID GUID
ServiceID GUID
}
// Utility function to build a struct sockaddr for syscalls.
func (a Addr) sockaddr(sa *rawSockaddrHyperv) (unsafe.Pointer, int32, error) {
sa.Family = hvsockAF
sa.Reserved = 0
for i := 0; i < len(sa.VMID); i++ {
sa.VMID[i] = a.VMID[i]
}
for i := 0; i < len(sa.ServiceID); i++ {
sa.ServiceID[i] = a.ServiceID[i]
}
return unsafe.Pointer(sa), int32(unsafe.Sizeof(*sa)), nil
}
// Help for read/write timeouts
type deadlineHandler struct {
setLock sync.Mutex
channel timeoutChan
channelLock sync.RWMutex
timer *time.Timer
timedout atomicBool
}
// The code below here is adjusted from:
// https://github.com/Microsoft/go-winio/blob/master/file.go
type atomicBool int32
func (b *atomicBool) isSet() bool { return atomic.LoadInt32((*int32)(b)) != 0 }
func (b *atomicBool) setFalse() { atomic.StoreInt32((*int32)(b), 0) }
func (b *atomicBool) setTrue() { atomic.StoreInt32((*int32)(b), 1) }
func (b *atomicBool) swap(new bool) bool {
var newInt int32
if new {
newInt = 1
}
return atomic.SwapInt32((*int32)(b), newInt) == 1
}
type timeoutError struct{}
func (e *timeoutError) Error() string { return "i/o timeout" }
func (e *timeoutError) Timeout() bool { return true }
func (e *timeoutError) Temporary() bool { return true }
type timeoutChan chan struct{}
var ioInitOnce sync.Once
var ioCompletionPort syscall.Handle
// ioResult contains the result of an asynchronous IO operation
type ioResult struct {
bytes uint32
err error
}
type ioOperation struct {
o syscall.Overlapped
ch chan ioResult
}
func initIo() {
h, err := createIoCompletionPort(syscall.InvalidHandle, 0, 0, 0xffffffff)
if err != nil {
panic(err)
}
ioCompletionPort = h
go ioCompletionProcessor(h)
}
func (v *hvsockConn) close() {
v.wgLock.Lock()
if !v.closing.swap(true) {
v.wgLock.Unlock()
// cancel all IO and wait for it to complete
cancelIoEx(v.fd, nil)
v.wg.Wait()
// at this point, no new IO can start
syscall.Close(v.fd)
v.fd = 0
} else {
v.wgLock.Unlock()
}
}
// prepareIo prepares for a new IO operation
func (v *hvsockConn) prepareIo() (*ioOperation, error) {
v.wgLock.RLock()
if v.closing.isSet() {
v.wgLock.RUnlock()
return nil, fmt.Errorf("HvSocket has already been closed")
}
v.wg.Add(1)
v.wgLock.RUnlock()
c := &ioOperation{}
c.ch = make(chan ioResult)
return c, nil
}
// ioCompletionProcessor processes completed async IOs forever
func ioCompletionProcessor(h syscall.Handle) {
// Set the timer resolution to 1. This fixes a performance regression in golang 1.6.
timeBeginPeriod(1)
for {
var bytes uint32
var key uintptr
var op *ioOperation
err := getQueuedCompletionStatus(h, &bytes, &key, &op, syscall.INFINITE)
if op == nil {
panic(err)
}
op.ch <- ioResult{bytes, err}
}
}
// asyncIo processes the return value from Recv or Send, blocking until
// the operation has actually completed.
func (v *hvsockConn) asyncIo(c *ioOperation, d *deadlineHandler, bytes uint32, err error) (int, error) {
if err != syscall.ERROR_IO_PENDING {
return int(bytes), err
}
if v.closing.isSet() {
cancelIoEx(v.fd, &c.o)
}
var timeout timeoutChan
if d != nil {
d.channelLock.Lock()
timeout = d.channel
d.channelLock.Unlock()
}
var r ioResult
select {
case r = <-c.ch:
err = r.err
if err == syscall.ERROR_OPERATION_ABORTED {
if v.closing.isSet() {
err = fmt.Errorf("HvSocket has already been closed")
}
}
case <-timeout:
cancelIoEx(v.fd, &c.o)
r = <-c.ch
err = r.err
if err == syscall.ERROR_OPERATION_ABORTED {
err = ErrTimeout
}
}
// runtime.KeepAlive is needed, as c is passed via native
// code to ioCompletionProcessor, c must remain alive
// until the channel read is complete.
runtime.KeepAlive(c)
return int(r.bytes), err
}
func (d *deadlineHandler) set(deadline time.Time) error {
d.setLock.Lock()
defer d.setLock.Unlock()
if d.timer != nil {
if !d.timer.Stop() {
<-d.channel
}
d.timer = nil
}
d.timedout.setFalse()
select {
case <-d.channel:
d.channelLock.Lock()
d.channel = make(chan struct{})
d.channelLock.Unlock()
default:
}
if deadline.IsZero() {
return nil
}
timeoutIO := func() {
d.timedout.setTrue()
close(d.channel)
}
now := time.Now()
duration := deadline.Sub(now)
if deadline.After(now) {
// Deadline is in the future, set a timer to wait
d.timer = time.AfterFunc(duration, timeoutIO)
} else {
// Deadline is in the past. Cancel all pending IO now.
timeoutIO()
}
return nil
}

View File

@ -0,0 +1,171 @@
package hvsock
/*
Most of this code was derived from: https://github.com/Microsoft/go-winio
which has the following license:
The MIT License (MIT)
Copyright (c) 2015 Microsoft
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
import (
"syscall"
"unsafe"
)
var (
modws2_32 = syscall.NewLazyDLL("ws2_32.dll")
modwinmm = syscall.NewLazyDLL("winmm.dll")
modkernel32 = syscall.NewLazyDLL("kernel32.dll")
procConnect = modws2_32.NewProc("connect")
procBind = modws2_32.NewProc("bind")
procAccept = modws2_32.NewProc("accept")
procCancelIoEx = modkernel32.NewProc("CancelIoEx")
procCreateIoCompletionPort = modkernel32.NewProc("CreateIoCompletionPort")
procGetQueuedCompletionStatus = modkernel32.NewProc("GetQueuedCompletionStatus")
procSetFileCompletionNotificationModes = modkernel32.NewProc("SetFileCompletionNotificationModes")
proctimeBeginPeriod = modwinmm.NewProc("timeBeginPeriod")
)
// Do the interface allocations only once for common
// Errno values.
const (
errnoERROR_IO_PENDING = 997
socketError = uintptr(^uint32(0))
cFILE_SKIP_COMPLETION_PORT_ON_SUCCESS = 1
cFILE_SKIP_SET_EVENT_ON_HANDLE = 2
)
var (
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
)
// errnoErr returns common boxed Errno values, to prevent
// allocations at runtime.
func errnoErr(e syscall.Errno) error {
switch e {
case 0:
return nil
case errnoERROR_IO_PENDING:
return errERROR_IO_PENDING
}
// TODO: add more here, after collecting data on the common
// error values see on Windows. (perhaps when running
// all.bat?)
return e
}
func sys_connect(s syscall.Handle, name unsafe.Pointer, namelen int32) (err error) {
r1, _, e1 := syscall.Syscall(procConnect.Addr(), 3, uintptr(s), uintptr(name), uintptr(namelen))
if r1 == socketError {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func sys_bind(s syscall.Handle, name unsafe.Pointer, namelen int32) (err error) {
r1, _, e1 := syscall.Syscall(procBind.Addr(), 3, uintptr(s), uintptr(name), uintptr(namelen))
if r1 == socketError {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func sys_accept(s syscall.Handle, rsa *rawSockaddrHyperv, addrlen *int32) (handle syscall.Handle, err error) {
r1, _, e1 := syscall.Syscall(procAccept.Addr(), 3, uintptr(s), uintptr(unsafe.Pointer(rsa)), uintptr(unsafe.Pointer(addrlen)))
handle = syscall.Handle(r1)
if r1 == socketError {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func cancelIoEx(file syscall.Handle, o *syscall.Overlapped) (err error) {
r1, _, e1 := syscall.Syscall(procCancelIoEx.Addr(), 2, uintptr(file), uintptr(unsafe.Pointer(o)), 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func createIoCompletionPort(file syscall.Handle, port syscall.Handle, key uintptr, threadCount uint32) (newport syscall.Handle, err error) {
r0, _, e1 := syscall.Syscall6(procCreateIoCompletionPort.Addr(), 4, uintptr(file), uintptr(port), uintptr(key), uintptr(threadCount), 0, 0)
newport = syscall.Handle(r0)
if newport == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func getQueuedCompletionStatus(port syscall.Handle, bytes *uint32, key *uintptr, o **ioOperation, timeout uint32) (err error) {
r1, _, e1 := syscall.Syscall6(procGetQueuedCompletionStatus.Addr(), 5, uintptr(port), uintptr(unsafe.Pointer(bytes)), uintptr(unsafe.Pointer(key)), uintptr(unsafe.Pointer(o)), uintptr(timeout), 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func setFileCompletionNotificationModes(h syscall.Handle, flags uint8) (err error) {
r1, _, e1 := syscall.Syscall(procSetFileCompletionNotificationModes.Addr(), 2, uintptr(h), uintptr(flags), 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func timeBeginPeriod(period uint32) (n int32) {
r0, _, _ := syscall.Syscall(proctimeBeginPeriod.Addr(), 1, uintptr(period), 0, 0)
n = int32(r0)
return
}

View File

@ -0,0 +1,24 @@
// +build !linux,!darwin
package vsock
import (
"fmt"
"log"
"net"
)
// SocketMode is the unimplemented fallback for unsupported OSes
func SocketMode(socketMode string) {
log.Fatalln("Unimplemented")
}
// Dial is the unimplemented fallback for unsupported OSes
func Dial(cid, port uint32) (Conn, error) {
return nil, fmt.Errorf("Unimplemented")
}
// Listen is the unimplemented fallback for unsupported OSes
func Listen(cid, port uint32) (net.Listener, error) {
return nil, fmt.Errorf("Unimplemented")
}

View File

@ -0,0 +1,66 @@
// Package vsock provides bindings to the hyperkit based
// implementation on macOS hosts. virtio Sockets are exposed as named
// pipes on macOS. Two modes are supported (to be set with
// SockerMode()):
// - Hyperkit mode: The package needs to be initialised with the path
// to where the named pipe was created.
// - Docker for Mac mode: This is a shortcut which hard codes the
// location of the named pipe.
package vsock
import (
"fmt"
"log"
"net"
"os"
"path/filepath"
"strings"
"github.com/pkg/errors"
)
var (
socketPath string
connectPath string
socketFmt string
)
// SocketMode initialises the bindings to either raw hyperkit mode
// ("hyperkit:/path") or Docker for Mac mode ("docker"). This function
// must be called before using the vsock bindings.
func SocketMode(socketMode string) {
socketFmt = "%08x.%08x"
if strings.HasPrefix(socketMode, "hyperkit:") {
socketPath = socketMode[len("hyperkit:"):]
} else if socketMode == "docker" {
socketPath = filepath.Join(os.Getenv("HOME"), "/Library/Containers/com.docker.docker/Data/vms/0")
} else {
log.Fatalln("Unknown socket mode: ", socketMode)
}
connectPath = filepath.Join(socketPath, "connect")
}
// Dial creates a connection to the VM with the given client ID and port
func Dial(cid, port uint32) (Conn, error) {
c, err := net.DialUnix("unix", nil, &net.UnixAddr{connectPath, "unix"})
if err != nil {
return c, errors.Wrapf(err, "failed to dial on %s", connectPath)
}
if _, err := fmt.Fprintf(c, "%08x.%08x\n", cid, port); err != nil {
return c, errors.Wrapf(err, "Failed to write dest (%08x.%08x) to %s", cid, port, connectPath)
}
return c, nil
}
// Listen creates a listener for a specifc vsock.
func Listen(cid, port uint32) (net.Listener, error) {
sock := filepath.Join(socketPath, fmt.Sprintf(socketFmt, cid, port))
if err := os.Remove(sock); err != nil && !os.IsNotExist(err) {
log.Fatalln("Listen(): Remove:", err)
return nil, err
}
return net.ListenUnix("unix", &net.UnixAddr{sock, "unix"})
}

View File

@ -0,0 +1,51 @@
// Package vsock provides the Linux guest bindings to VM sockets. VM
// sockets are a generic mechanism for guest<->host communication. It
// was originally developed for VMware but now also supports virtio
// sockets and (soon) Hyper-V sockets.
//
// The main purpose is to provide bindings to the Linux implementation
// of VM sockets, based on the low level support in
// golang.org/x/sys/unix.
//
// The package also provides bindings to the host interface to virtio
// sockets for HyperKit on macOS.
package vsock
import (
"fmt"
"net"
"os"
)
const (
// CIDAny is a wildcard CID
CIDAny = 4294967295 // 2^32-1
// CIDHypervisor is the reserved CID for the Hypervisor
CIDHypervisor = 0
// CIDHost is the reserved CID for the host system
CIDHost = 2
)
// Addr represents the address of a vsock end point.
type Addr struct {
CID uint32
Port uint32
}
// Network returns the network type for a Addr
func (a Addr) Network() string {
return "vsock"
}
// String returns a string representation of a Addr
func (a Addr) String() string {
return fmt.Sprintf("%08x.%08x", a.CID, a.Port)
}
// Conn is a vsock connection which supports half-close.
type Conn interface {
net.Conn
CloseRead() error
CloseWrite() error
File() (*os.File, error)
}

View File

@ -0,0 +1,164 @@
// Bindings to the Linux hues interface to VM sockets.
package vsock
import (
"fmt"
"net"
"os"
"syscall"
"time"
"github.com/pkg/errors"
"golang.org/x/sys/unix"
)
// SocketMode is a NOOP on Linux
func SocketMode(m string) {
}
// Convert a generic unix.Sockaddr to a Addr
func sockaddrToVsock(sa unix.Sockaddr) *Addr {
switch sa := sa.(type) {
case *unix.SockaddrVM:
return &Addr{CID: sa.CID, Port: sa.Port}
}
return nil
}
// Dial connects to the CID.Port via virtio sockets
func Dial(cid, port uint32) (Conn, error) {
fd, err := syscall.Socket(unix.AF_VSOCK, syscall.SOCK_STREAM|syscall.SOCK_CLOEXEC, 0)
if err != nil {
return nil, errors.Wrap(err, "Failed to create AF_VSOCK socket")
}
sa := &unix.SockaddrVM{CID: cid, Port: port}
// Retry connect in a loop if EINTR is encountered.
for {
if err := unix.Connect(fd, sa); err != nil {
if errno, ok := err.(syscall.Errno); ok && errno == syscall.EINTR {
continue
}
return nil, errors.Wrapf(err, "failed connect() to %08x.%08x", cid, port)
}
break
}
return newVsockConn(uintptr(fd), nil, &Addr{cid, port}), nil
}
// Listen returns a net.Listener which can accept connections on the given port
func Listen(cid, port uint32) (net.Listener, error) {
fd, err := syscall.Socket(unix.AF_VSOCK, syscall.SOCK_STREAM|syscall.SOCK_CLOEXEC, 0)
if err != nil {
return nil, err
}
sa := &unix.SockaddrVM{CID: cid, Port: port}
if err = unix.Bind(fd, sa); err != nil {
return nil, errors.Wrapf(err, "bind() to %08x.%08x failed", cid, port)
}
err = syscall.Listen(fd, syscall.SOMAXCONN)
if err != nil {
return nil, errors.Wrapf(err, "listen() on %08x.%08x failed", cid, port)
}
return &vsockListener{fd, Addr{cid, port}}, nil
}
type vsockListener struct {
fd int
local Addr
}
// Accept accepts an incoming call and returns the new connection.
func (v *vsockListener) Accept() (net.Conn, error) {
fd, sa, err := unix.Accept(v.fd)
if err != nil {
return nil, err
}
return newVsockConn(uintptr(fd), &v.local, sockaddrToVsock(sa)), nil
}
// Close closes the listening connection
func (v *vsockListener) Close() error {
// Note this won't cause the Accept to unblock.
return unix.Close(v.fd)
}
// Addr returns the address the Listener is listening on
func (v *vsockListener) Addr() net.Addr {
return v.local
}
// a wrapper around FileConn which supports CloseRead and CloseWrite
type vsockConn struct {
vsock *os.File
fd uintptr
local *Addr
remote *Addr
}
func newVsockConn(fd uintptr, local, remote *Addr) *vsockConn {
vsock := os.NewFile(fd, fmt.Sprintf("vsock:%d", fd))
return &vsockConn{vsock: vsock, fd: fd, local: local, remote: remote}
}
// LocalAddr returns the local address of a connection
func (v *vsockConn) LocalAddr() net.Addr {
return v.local
}
// RemoteAddr returns the remote address of a connection
func (v *vsockConn) RemoteAddr() net.Addr {
return v.remote
}
// Close closes the connection
func (v *vsockConn) Close() error {
return v.vsock.Close()
}
// CloseRead shuts down the reading side of a vsock connection
func (v *vsockConn) CloseRead() error {
return syscall.Shutdown(int(v.fd), syscall.SHUT_RD)
}
// CloseWrite shuts down the writing side of a vsock connection
func (v *vsockConn) CloseWrite() error {
return syscall.Shutdown(int(v.fd), syscall.SHUT_WR)
}
// Read reads data from the connection
func (v *vsockConn) Read(buf []byte) (int, error) {
return v.vsock.Read(buf)
}
// Write writes data over the connection
func (v *vsockConn) Write(buf []byte) (int, error) {
return v.vsock.Write(buf)
}
// SetDeadline sets the read and write deadlines associated with the connection
func (v *vsockConn) SetDeadline(t time.Time) error {
return nil // FIXME
}
// SetReadDeadline sets the deadline for future Read calls.
func (v *vsockConn) SetReadDeadline(t time.Time) error {
return nil // FIXME
}
// SetWriteDeadline sets the deadline for future Write calls
func (v *vsockConn) SetWriteDeadline(t time.Time) error {
return nil // FIXME
}
// File duplicates the underlying socket descriptor and returns it.
func (v *vsockConn) File() (*os.File, error) {
// This is equivalent to dup(2) but creates the new fd with CLOEXEC already set.
r0, _, e1 := syscall.Syscall(syscall.SYS_FCNTL, uintptr(v.vsock.Fd()), syscall.F_DUPFD_CLOEXEC, 0)
if e1 != 0 {
return nil, os.NewSyscallError("fcntl", e1)
}
return os.NewFile(r0, v.vsock.Name()), nil
}

View File

@ -0,0 +1,2 @@
github.com/pkg/errors 2b3a18b5f0fb6b4f9190549597d3f962c02bc5eb
golang.org/x/sys 9c9d83fe39ed3fd2d9249fcf6b755891fff54b03

View File

@ -1,191 +0,0 @@
Apache License
Version 2.0, January 2004
https://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
Copyright 2013-2016 Docker, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -1,112 +0,0 @@
## DataKit -- Orchestrate applications using a Git-like dataflow
*DataKit* is a tool to orchestrate applications using a Git-like dataflow. It
revisits the UNIX pipeline concept, with a modern twist: streams of
tree-structured data instead of raw text. DataKit allows you to define
complex build pipelines over version-controlled data.
DataKit is currently used as the coordination
layer for [HyperKit](http://github.com/docker/hyperkit), the
hypervisor component of
[Docker for Mac and Windows](https://blog.docker.com/2016/03/docker-for-mac-windows-beta/), and
for the [DataKitCI][] continuous integration system.
---
[![Build Status (OSX, Linux)](https://travis-ci.org/moby/datakit.svg)](https://travis-ci.org/moby/datakit)
[![Build status (Windows)](https://ci.appveyor.com/api/projects/status/6qrdgiqbhi4sehmy/branch/master?svg=true)](https://ci.appveyor.com/project/moby/datakit/branch/master)
[![docs](https://img.shields.io/badge/doc-online-blue.svg)](https://docker.github.io/datakit/)
There are several components in this repository:
- `src` contains the main DataKit service. This is a Git-like database to which other services can connect.
- `ci` contains [DataKitCI][], a continuous integration system that uses DataKit to monitor repositories and store build results.
- `ci/self-ci` is the CI configuration for DataKitCI that tests DataKit itself.
- `bridge/github` is a service that monitors repositories on GitHub and syncs their metadata with a DataKit database.
e.g. when a pull request is opened or updated, it will commit that information to DataKit. If you commit a status message to DataKit, the bridge will push it to GitHub.
- `bridge/local` is a drop-in replacement for `bridge/github` that just monitors a local Git repository. This is useful for local testing.
### Quick Start
The easiest way to use DataKit is to start both the server and the client in containers.
To expose a Git repository as a 9p endpoint on port 5640 on a private network, run:
```shell
$ docker network create datakit-net # create a private network
$ docker run -it --net datakit-net --name datakit -v <path/to/git/repo>:/data datakit/db
```
*Note*: The `--name datakit` option is mandatory. It will allow the client
to connect to a known name on the private network.
You can then start a DataKit client, which will mount the 9p endpoint and
expose the database as a filesystem API:
```shell
# In an other terminal
$ docker run -it --privileged --net datakit-net datakit/client
$ ls /db
branch remotes snapshots trees
```
*Note*: the `--privileged` option is needed because the container will have
to mount the 9p endpoint into its local filesystem.
Now you can explore, edit and script `/db`. See the
[Filesystem API][]
for more details.
### Building
The easiest way to build the DataKit project is to use [docker](https://docker.com),
(which is what the
[start-datakit.sh](https://github.com/moby/datakit/blob/master/scripts/start-datakit.sh) script
does under the hood):
```shell
docker build -t datakit/db -f Dockerfile .
docker run -p 5640:5640 -it --rm datakit/db --listen-9p=tcp://0.0.0.0:5640
```
These commands will expose the database's 9p endpoint on port 5640.
If you want to build the project from source without Docker, you will need to install
[ocaml](http://ocaml.org/) and [opam](http://opam.ocaml.org/). Then write:
```shell
$ make depends
$ make && make test
```
For information about command-line options:
```shell
$ datakit --help
```
## Prometheus metric reporting
Run with `--listen-prometheus 9090` to expose metrics at `http://*:9090/metrics`.
Note: there is no encryption and no access control. You are expected to run the
database in a container and to not export this port to the outside world. You
can either collect the metrics by running a Prometheus service in a container
on the same Docker network, or front the service with nginx or similar if you
want to collect metrics remotely.
## Language bindings
* **Go** bindings are in the `api/go` directory.
* **OCaml** bindings are in the `api/ocaml` directory. See `examples/ocaml-client` for an example.
## Licensing
DataKit is licensed under the Apache License, Version 2.0. See
[LICENSE](https://github.com/moby/datakit/blob/master/LICENSE.md) for the full
license text.
Contributions are welcome under the terms of this license. You may wish to browse
the [weekly reports](reports) to read about overall activity in the repository.
[DataKitCI]: https://github.com/moby/datakit/tree/master/ci
[Filesystem API]: https://github.com/moby/datakit/tree/master/9p.md

View File

@ -1 +0,0 @@
To run test on windows, launch the a datakit server with --url \\.\pipe\datakit-test

View File

@ -1,352 +0,0 @@
package datakit
import (
"bytes"
"io"
"log"
"net"
"sync"
p9p "github.com/docker/go-p9p"
"context"
)
type Client struct {
conn net.Conn
session p9p.Session
m *sync.Mutex
c *sync.Cond
usedFids map[p9p.Fid]bool
freeFids []p9p.Fid
root p9p.Fid
}
var badFid = p9p.Fid(0)
var rwx = p9p.DMREAD | p9p.DMWRITE | p9p.DMEXEC
var rx = p9p.DMREAD | p9p.DMEXEC
var rw = p9p.DMREAD | p9p.DMWRITE
var r = p9p.DMREAD
var dirperm = uint32(rwx<<6 | rx<<3 | rx | p9p.DMDIR)
var fileperm = uint32(rw<<6 | r<<3 | r)
// Dial opens a connection to a 9P server
func Dial(ctx context.Context, network, address string) (*Client, error) {
log.Println("Dialling", network, address)
conn, err := net.Dial(network, address)
if err != nil {
return nil, err
}
return NewClient(ctx, conn)
}
// NewClient creates opens a connection with the p9p server
func NewClient(ctx context.Context, conn net.Conn) (*Client, error) {
session, err := p9p.NewSession(ctx, conn)
if err != nil {
log.Println("Failed to establish 9P session to", err)
return nil, err
}
root := p9p.Fid(1)
if _, err := session.Attach(ctx, root, p9p.NOFID, "anyone", "/"); err != nil {
log.Println("Failed to Attach to filesystem", err)
return nil, err
}
usedFids := make(map[p9p.Fid]bool, 0)
freeFids := make([]p9p.Fid, 0)
for i := 0; i < 128; i++ {
fid := p9p.Fid(i)
if fid == root {
usedFids[fid] = true
} else {
freeFids = append(freeFids, fid)
usedFids[fid] = false
}
}
var m sync.Mutex
c := sync.NewCond(&m)
return &Client{conn, session, &m, c, usedFids, freeFids, root}, nil
}
func (c *Client) Close(ctx context.Context) {
if err := c.session.Clunk(ctx, c.root); err != nil {
log.Println("Failed to Clunk root fid", err)
} else {
c.usedFids[c.root] = false
}
c.m.Lock()
defer c.m.Unlock()
for fid, inuse := range c.usedFids {
if inuse {
log.Println("I don't know how to flush: leaking", fid)
}
}
c.conn.Close()
}
// allocFid returns a fresh fid, bound to a clone of from
func (c *Client) allocFid(ctx context.Context, from p9p.Fid) (p9p.Fid, error) {
c.m.Lock()
defer c.m.Unlock()
for len(c.freeFids) == 0 {
c.c.Wait()
}
fid := c.freeFids[len(c.freeFids)-1]
c.freeFids = c.freeFids[0 : len(c.freeFids)-1]
c.usedFids[fid] = true
_, err := c.session.Walk(ctx, from, fid)
if err != nil {
log.Println("Failed to clone root fid", err)
return badFid, err
}
return fid, nil
}
// freeFid removes resources associated with the given fid
func (c *Client) freeFid(ctx context.Context, fid p9p.Fid) {
c.m.Lock()
defer c.m.Unlock()
c.freeFids = append(c.freeFids, fid)
c.usedFids[fid] = false
if err := c.session.Clunk(ctx, fid); err != nil {
log.Println("Failed to clunk fid", fid)
}
c.c.Signal()
}
// Mkdir acts like 'mkdir -p'
func (c *Client) Mkdir(ctx context.Context, path ...string) error {
fid, err := c.allocFid(ctx, c.root)
if err != nil {
return nil
}
defer c.freeFid(ctx, fid)
// mkdir -p
for _, dir := range path {
dirfid, err := c.allocFid(ctx, fid)
if err != nil {
return err
}
// dir may or may not exist
_, _, _ = c.session.Create(ctx, dirfid, dir, dirperm, p9p.OREAD)
c.freeFid(ctx, dirfid)
// dir should definitely exist
if _, err := c.session.Walk(ctx, fid, fid, dir); err != nil {
log.Println("Failed to Walk to", dir, err)
return err
}
}
return nil
}
var enoent = p9p.MessageRerror{Ename: "No such file or directory"}
var enotdir = p9p.MessageRerror{Ename: "Can't walk from a file"}
// Remove acts like 'rm -f'
func (c *Client) Remove(ctx context.Context, path ...string) error {
fid, err := c.allocFid(ctx, c.root)
if err != nil {
return err
}
if _, err := c.session.Walk(ctx, fid, fid, path...); err != nil {
if err == enoent || err == enotdir {
c.freeFid(ctx, fid)
return nil
}
log.Println("Failed to walk to", path, err)
c.freeFid(ctx, fid)
return err
}
// Remove will cluck the fid, even if it fails
if err := c.session.Remove(ctx, fid); err != nil {
if err == enoent {
return nil
}
log.Println("Failed to Remove", path, err)
return err
}
return nil
}
type File struct {
fid p9p.Fid
c *Client
m *sync.Mutex
open bool
}
// Create creates a file
func (c *Client) Create(ctx context.Context, path ...string) (*File, error) {
fid, err := c.allocFid(ctx, c.root)
if err != nil {
return nil, err
}
dir := path[0 : len(path)-1]
_, err = c.session.Walk(ctx, fid, fid, dir...)
if err != nil {
if err != enoent {
// This is a common error
log.Println("Failed to Walk to", path, err)
}
c.freeFid(ctx, fid)
return nil, err
}
_, _, err = c.session.Create(ctx, fid, path[len(path)-1], fileperm, p9p.ORDWR)
if err != nil {
log.Println("Failed to Create", path, err)
return nil, err
}
var m sync.Mutex
return &File{fid: fid, c: c, m: &m, open: true}, nil
}
// Open opens a file
func (c *Client) Open(ctx context.Context, mode p9p.Flag, path ...string) (*File, error) {
fid, err := c.allocFid(ctx, c.root)
if err != nil {
return nil, err
}
_, err = c.session.Walk(ctx, fid, fid, path...)
if err != nil {
if err != enoent {
// This is a common error
log.Println("Failed to Walk to", path, err)
}
c.freeFid(ctx, fid)
return nil, err
}
_, _, err = c.session.Open(ctx, fid, mode)
if err != nil {
log.Println("Failed to Open", path, err)
c.freeFid(ctx, fid)
return nil, err
}
var m sync.Mutex
return &File{fid: fid, c: c, m: &m, open: true}, nil
}
// List a directory
func (c *Client) List(ctx context.Context, path []string) ([]string, error) {
file, err := c.Open(ctx, p9p.OREAD, path...)
if err != nil {
return nil, err
}
defer file.Close(ctx)
msize, _ := c.session.Version()
iounit := uint32(msize - 24) // size of message max minus fcall io header (Rread)
p := make([]byte, iounit)
n, err := c.session.Read(ctx, file.fid, p, 0)
if err != nil {
return nil, err
}
files := []string{}
rd := bytes.NewReader(p[:n])
codec := p9p.NewCodec() // TODO(stevvooe): Need way to resolve codec based on session.
for {
var d p9p.Dir
if err := p9p.DecodeDir(codec, rd, &d); err != nil {
if err == io.EOF {
break
}
return files, err
}
files = append(files, d.Name)
}
return files, nil
}
// Close closes a file
func (f *File) Close(ctx context.Context) {
f.m.Lock()
defer f.m.Unlock()
if f.open {
f.c.freeFid(ctx, f.fid)
}
f.open = false
}
// Read reads a value
func (f *File) Read(ctx context.Context, p []byte, offset int64) (int, error) {
f.m.Lock()
defer f.m.Unlock()
if !f.open {
return 0, io.EOF
}
return f.c.session.Read(ctx, f.fid, p, offset)
}
// Write writes a value
func (f *File) Write(ctx context.Context, p []byte, offset int64) (int, error) {
f.m.Lock()
defer f.m.Unlock()
if !f.open {
return 0, io.EOF
}
return f.c.session.Write(ctx, f.fid, p, offset)
}
type FileReader struct {
file *File
offset int64
ctx context.Context
}
func (f *File) NewFileReader(ctx context.Context) *FileReader {
offset := int64(0)
return &FileReader{file: f, offset: offset, ctx: ctx}
}
func (f *FileReader) Read(p []byte) (int, error) {
n, err := f.file.Read(f.ctx, p, f.offset)
f.offset = f.offset + int64(n)
if n == 0 {
return 0, io.EOF
}
return n, err
}
type ioFileReaderWriter struct {
f *File
ctx context.Context
offset int64
}
// NewIOReader creates a standard io.Reader at a given position in the file
func (f *File) NewIOReader(ctx context.Context, offset int64) io.Reader {
return &ioFileReaderWriter{f, ctx, offset}
}
// NewIOWriter creates a standard io.Writer at a given position in the file
func (f *File) NewIOWriter(ctx context.Context, offset int64) io.Writer {
return &ioFileReaderWriter{f, ctx, offset}
}
func (r *ioFileReaderWriter) Read(p []byte) (n int, err error) {
r.f.m.Lock()
defer r.f.m.Unlock()
n, err = r.f.c.session.Read(r.ctx, r.f.fid, p, r.offset)
r.offset += int64(n)
return n, err
}
func (w *ioFileReaderWriter) Write(p []byte) (n int, err error) {
w.f.m.Lock()
defer w.f.m.Unlock()
for err == nil || err == io.ErrShortWrite {
var written int
written, err = w.f.c.session.Write(w.ctx, w.f.fid, p, w.offset)
p = p[written:]
w.offset += int64(written)
n += written
if len(p) == 0 {
break
}
}
return
}

View File

@ -1,380 +0,0 @@
package datakit
import (
"fmt"
"log"
"strconv"
"strings"
"context"
)
type Version int
var InitialVersion = Version(0)
// Record is a typed view on top of a database branch
type Record struct {
client *Client
path []string // directory inside the store
version Version
schemaF *IntField
fields []*StringRefField // registered fields, for schema upgrades
lookupB []string // priority ordered list of branches to look up values in
defaultsB string // name of the branch containing built-in defaults
stateB string // name of the branch containing run-time state
event chan (interface{})
onUpdate [](func([]*Snapshot, Version))
}
func NewRecord(ctx context.Context, client *Client, lookupB []string, defaultsB string, stateB string, path []string) (*Record, error) {
event := make(chan (interface{}), 0)
for _, b := range append(lookupB, stateB) {
// Create the branch if it doesn't exist
t, err := NewTransaction(ctx, client, b)
if err != nil {
log.Fatalf("Failed to open a new transaction: %#v", err)
}
if err = t.Write(ctx, []string{"branch-created"}, ""); err != nil {
log.Fatalf("Failed to write branch-created: %#v", err)
}
if err = t.Commit(ctx, "Creating branch"); err != nil {
log.Fatalf("Failed to commit transaction: %#v", err)
}
}
for _, b := range lookupB {
if err := client.Mkdir(ctx, "branch", b); err != nil {
return nil, err
}
w, err := NewWatch(ctx, client, b, path)
if err != nil {
return nil, err
}
go func() {
for {
_, err := w.Next(ctx)
if err != nil {
return
}
log.Printf("Snapshot has changed\n")
event <- 0
}
}()
}
onUpdate := make([](func([]*Snapshot, Version)), 0)
fields := make([]*StringRefField, 0)
r := &Record{
client: client,
path: path,
version: InitialVersion,
fields: fields,
lookupB: lookupB,
defaultsB: defaultsB,
stateB: stateB,
event: event,
onUpdate: onUpdate,
}
r.schemaF = r.IntField("schema-version", 1)
return r, nil
}
func (r *Record) updateAll(ctx context.Context) error {
snapshots := make([]*Snapshot, 0)
for _, b := range r.lookupB {
head, err := Head(ctx, r.client, b)
if err != nil {
return err
}
snap := NewSnapshot(ctx, r.client, COMMIT, head)
snapshots = append(snapshots, snap)
}
for _, fn := range r.onUpdate {
fn(snapshots, r.version)
}
return nil
}
func (r *Record) Seal(ctx context.Context) error {
return r.updateAll(ctx)
}
func (r *Record) Wait(ctx context.Context) error {
<-r.event
r.version = r.version + 1
return r.updateAll(ctx)
}
func (r *Record) Upgrade(ctx context.Context, schemaVersion int) error {
currentVersion, _ := r.schemaF.Get()
if schemaVersion <= currentVersion {
log.Printf("No schema upgrade necessary because new version (%d) <= current version (%d)\n", schemaVersion, currentVersion)
return nil
}
r.schemaF.defaultInt = schemaVersion
defaultString := fmt.Sprintf("%d", schemaVersion)
r.schemaF.raw.defaultValue = &defaultString
// Create defaults branch
log.Printf("Performing schema upgrade to version %d\n", schemaVersion)
t, err := NewTransaction(ctx, r.client, r.defaultsB)
if err != nil {
return err
}
// For each known field, write default value to branch
for _, f := range r.fields {
p := append(r.path, f.path...)
if f.defaultValue == nil {
err = t.Remove(ctx, p)
} else {
err = t.Write(ctx, p, *f.defaultValue)
}
if err != nil {
return err
}
}
// Merge to the defaults branch
err = t.Commit(ctx, fmt.Sprintf("Upgrade to schema version %d", schemaVersion))
if err != nil {
return err
}
return r.Wait(ctx)
}
// fillInDefault updates the default branch to contain the new value.
func (r *Record) fillInDefault(path []string, valueref *string) error {
ctx := context.Background()
t, err := NewTransaction(ctx, r.client, r.defaultsB)
if err != nil {
return err
}
p := append(r.path, path...)
if valueref == nil {
log.Printf("Removing default value at %s", strings.Join(p, "/"))
if err = t.Remove(ctx, p); err != nil {
log.Printf("Failed to remove key at %s", strings.Join(p, "/"))
return err
}
} else {
log.Printf("Updating default value at %s to %s", strings.Join(p, "/"), *valueref)
if err = t.Write(ctx, p, *valueref); err != nil {
log.Printf("Failed to write key %s = %s", strings.Join(p, "/"), *valueref)
return err
}
}
return t.Commit(ctx, fmt.Sprintf("fill-in default for %s", p))
}
func (r *Record) SetMultiple(description string, fields []*StringField, values []string) error {
if len(fields) != len(values) {
return fmt.Errorf("Length of fields and values is not equal")
}
ctx := context.Background()
t, err := NewTransaction(ctx, r.client, r.lookupB[0])
if err != nil {
return err
}
for i, k := range fields {
p := append(r.path, k.raw.path...)
v := values[i]
log.Printf("Setting value in store: %#v=%s\n", p, v)
err = t.Write(ctx, p, v)
if err != nil {
return err
}
}
return t.Commit(ctx, description)
}
type StringRefField struct {
path []string
value *string
defaultValue *string
version Version // version of last change
record *Record
}
// Set unconditionally sets the value of the key
func (f *StringRefField) Set(description string, value *string) error {
// TODO: maybe this should return Version, too?
ctx := context.Background()
p := append(f.record.path, f.path...)
log.Printf("Setting value in store: %#v=%#v\n", p, value)
t, err := NewTransaction(ctx, f.record.client, f.record.lookupB[0])
if err != nil {
return err
}
if value == nil {
err = t.Remove(ctx, p)
} else {
err = t.Write(ctx, p, *value)
}
if err != nil {
return err
}
return t.Commit(ctx, fmt.Sprintf("Unconditionally set %s: %s", f.path, description))
}
// Get retrieves the current value of the key
func (f *StringRefField) Get() (*string, Version) {
if f.value == nil {
return nil, f.version
}
raw := strings.TrimSpace(*f.value)
return &raw, f.version
}
// HasChanged returns true if the key has changed since the given version
func (f *StringRefField) HasChanged(version Version) bool {
return version < f.version
}
// StringRefField defines a string option which can be nil with a specified
// key and default value
func (f *Record) StringRefField(key string, value *string) *StringRefField {
path := strings.Split(key, "/")
field := &StringRefField{path: path, value: value, defaultValue: value, version: InitialVersion, record: f}
// If the value is not in the database, write the default Value.
err := f.fillInDefault(path, value)
if err != nil {
log.Println("Failed to write default value", key, "=", value)
}
fn := func(snaps []*Snapshot, version Version) {
ctx := context.Background()
var newValue *string
for _, snap := range snaps {
v, err := snap.Read(ctx, append(f.path, path...))
if err != nil {
if err != enoent {
log.Println("Failed to read key", key, "from directory snapshot", snap)
return
}
// if enoent then newValue == nil
} else {
newValue = &v
break
}
}
if (field.value == nil && newValue != nil) || (field.value != nil && newValue == nil) || (field.value != nil && newValue != nil && *field.value != *newValue) {
field.value = newValue
field.version = version
}
// Update the value in memory and in the state branch
t, err := NewTransaction(ctx, f.client, f.stateB)
if err != nil {
log.Fatalf("Failed to create transaction for updating state branch: %#v", err)
}
if newValue != nil {
if err = t.Write(ctx, append(f.path, path...), *newValue); err != nil {
log.Fatalf("Failed to write state %#v = %s: %#v", path, *newValue, err)
}
} else {
if err = t.Remove(ctx, append(f.path, path...)); err != nil {
log.Fatalf("Failed to remove state %#v: %#v", path, err)
}
}
if err = t.Commit(ctx, "Updating state branch"); err != nil {
log.Fatalf("Failed to commit transaction: %#v", err)
}
}
f.onUpdate = append(f.onUpdate, fn)
//fn(f.version)
f.fields = append(f.fields, field)
return field
}
type StringField struct {
raw *StringRefField
defaultString string
}
// Get retrieves the current value of the key
func (f *StringField) Get() (string, Version) {
if f.raw.value == nil {
log.Printf("Failed to find string in database at %s, defaulting to %s", strings.Join(f.raw.path, "/"), f.defaultString)
return f.defaultString, f.raw.version
}
return *f.raw.value, f.raw.version
}
// Set unconditionally sets the value of the key
func (f *StringField) Set(description string, value string) error {
return f.raw.Set(description, &value)
}
// HasChanged returns true if the key has changed since the given version
func (f *StringField) HasChanged(version Version) bool {
return version < f.raw.version
}
// StringField defines a string
func (f *Record) StringField(key string, value string) *StringField {
raw := f.StringRefField(key, &value)
return &StringField{raw: raw, defaultString: value}
}
type IntField struct {
raw *StringRefField
defaultInt int
}
// Get retrieves the current value of the key
func (f *IntField) Get() (int, Version) {
if f.raw.value == nil {
log.Printf("Key %s missing in database, defaulting value to %t", strings.Join(f.raw.path, "/"), f.defaultInt)
return f.defaultInt, f.raw.version
}
value64, err := strconv.ParseInt(strings.TrimSpace(*f.raw.value), 10, 0)
if err != nil {
// revert to default if we can't parse the result
log.Printf("Failed to parse int in database: '%s', defaulting to %d", f.raw.value, f.defaultInt)
return f.defaultInt, f.raw.version
}
return int(value64), f.raw.version
}
// HasChanged returns true if the key has changed since the given version
func (f *IntField) HasChanged(version Version) bool {
return version < f.raw.version
}
// IntField defines an boolean option with a specified key and default value
func (f *Record) IntField(key string, value int) *IntField {
stringValue := fmt.Sprintf("%d", value)
raw := f.StringRefField(key, &stringValue)
return &IntField{raw: raw, defaultInt: value}
}
type BoolField struct {
raw *StringRefField
defaultBool bool
}
// Get retrieves the current value of the key
func (f *BoolField) Get() (bool, Version) {
if f.raw.value == nil {
log.Printf("Key %s missing in database, defaulting value to %t", strings.Join(f.raw.path, "/"), f.defaultBool)
return f.defaultBool, f.raw.version
}
value, err := strconv.ParseBool(strings.TrimSpace(*f.raw.value))
if err != nil {
// revert to default if we can't parse the result
log.Printf("Failed to parse boolean in database: '%s', defaulting to %t", f.raw.value, f.defaultBool)
return f.defaultBool, f.raw.version
}
return value, f.raw.version
}
// HasChanged returns true if the key has changed since the given version
func (f *BoolField) HasChanged(version Version) bool {
return version < f.raw.version
}
// BoolField defines an boolean option with a specified key and default value
func (f *Record) BoolField(key string, value bool) *BoolField {
stringValue := fmt.Sprintf("%t", value)
raw := f.StringRefField(key, &stringValue)
return &BoolField{raw: raw, defaultBool: value}
}

View File

@ -1,5 +0,0 @@
/*
The datakit package contains common patterns over 9P, which avoids the need
for applications to use 9P directly.
*/
package datakit

View File

@ -1,87 +0,0 @@
package datakit
import (
"bytes"
"io"
"log"
"strings"
p9p "github.com/docker/go-p9p"
"context"
)
type SnapshotKind uint8
const (
COMMIT SnapshotKind = 0x01 // from a commit hash
OBJECT SnapshotKind = 0x02 // from an object hash
)
type snapshot struct {
client *Client
kind SnapshotKind
thing string
}
type Snapshot struct {
snapshot
}
// NewSnapshot opens a new snapshot referencing the given object.
func NewSnapshot(ctx context.Context, client *Client, kind SnapshotKind, thing string) *Snapshot {
return &Snapshot{snapshot{client: client, kind: kind, thing: thing}}
}
// Head retrieves the commit sha of the given branch
func Head(ctx context.Context, client *Client, fromBranch string) (string, error) {
// SHA=$(cat branch/<fromBranch>/head)
file, err := client.Open(ctx, p9p.ORDWR, "branch", fromBranch, "head")
if err != nil {
log.Println("Failed to open branch/", fromBranch, "/head")
return "", err
}
defer file.Close(ctx)
buf := make([]byte, 512)
n, err := file.Read(ctx, buf, 0)
if err != nil {
log.Println("Failed to Read branch", fromBranch, "head", err)
return "", err
}
return strings.TrimSpace(string(buf[0:n])), nil
}
func (s *Snapshot) getFullPath(path []string) []string {
var p []string
switch s.kind {
case COMMIT:
p = []string{"snapshots", s.thing, "ro"}
case OBJECT:
p = []string{"trees", s.thing}
}
for _, element := range path {
p = append(p, element)
}
return p
}
// Read reads a value from the snapshot
func (s *Snapshot) Read(ctx context.Context, path []string) (string, error) {
p := s.getFullPath(path)
file, err := s.client.Open(ctx, p9p.OREAD, p...)
if err != nil {
return "", err
}
defer file.Close(ctx)
reader := file.NewIOReader(ctx, 0)
buf := bytes.NewBuffer(nil)
io.Copy(buf, reader)
return string(buf.Bytes()), nil
}
// List returns filenames list in directory
func (s *Snapshot) List(ctx context.Context, path []string) ([]string, error) {
p := s.getFullPath(path)
return s.client.List(ctx, p)
}

View File

@ -1,136 +0,0 @@
package datakit
import (
"bytes"
"io"
"log"
"strconv"
"sync/atomic"
p9p "github.com/docker/go-p9p"
"context"
)
type transaction struct {
client *Client
fromBranch string
newBranch string
}
var nextTransaction = int64(0)
// NewTransaction opens a new transaction originating from fromBranch, named
// newBranch.
func NewTransaction(ctx context.Context, client *Client, fromBranch string) (*transaction, error) {
id := atomic.AddInt64(&nextTransaction, 1)
newBranch := strconv.FormatInt(id, 10)
err := client.Mkdir(ctx, "branch", fromBranch)
if err != nil {
log.Println("Failed to Create branch/", fromBranch, err)
return nil, err
}
err = client.Mkdir(ctx, "branch", fromBranch, "transactions", newBranch)
if err != nil {
log.Println("Failed to Create branch/", fromBranch, "/transactions/", newBranch, err)
return nil, err
}
return &transaction{client: client, fromBranch: fromBranch, newBranch: newBranch}, nil
}
func (t *transaction) close(ctx context.Context) {
// TODO: do we need to clear up unmerged branches?
}
// Abort ensures the update will not be committed.
func (t *transaction) Abort(ctx context.Context) {
t.close(ctx)
}
// Commit merges the newBranch back into the fromBranch, or fails
func (t *transaction) Commit(ctx context.Context, msg string) error {
// msg
msgPath := []string{"branch", t.fromBranch, "transactions", t.newBranch, "msg"}
msgFile, err := t.client.Open(ctx, p9p.ORDWR, msgPath...)
if err != nil {
log.Println("Failed to Open msg", err)
return err
}
defer msgFile.Close(ctx)
_, err = msgFile.Write(ctx, []byte(msg), 0)
if err != nil {
log.Println("Failed to Write msg", err)
}
// ctl
ctlPath := []string{"branch", t.fromBranch, "transactions", t.newBranch, "ctl"}
ctlFile, err := t.client.Open(ctx, p9p.ORDWR, ctlPath...)
if err != nil {
log.Println("Failed to Open ctl", err)
return err
}
defer ctlFile.Close(ctx)
_, err = ctlFile.Write(ctx, []byte("commit"), 0)
if err != nil {
log.Println("Failed to Write ctl", err)
return err
}
return nil
}
// Write updates a key=value pair within the transaction.
func (t *transaction) Write(ctx context.Context, path []string, value string) error {
p := []string{"branch", t.fromBranch, "transactions", t.newBranch, "rw"}
for _, dir := range path[0 : len(path)-1] {
p = append(p, dir)
}
err := t.client.Mkdir(ctx, p...)
if err != nil {
log.Println("Failed to Mkdir", p)
}
p = append(p, path[len(path)-1])
file, err := t.client.Create(ctx, p...)
if err != nil {
log.Println("Failed to Create", p)
return err
}
defer file.Close(ctx)
writer := file.NewIOWriter(ctx, 0)
_, err = writer.Write([]byte(value))
if err != nil {
log.Println("Failed to Write", path, "=", value, ":", err)
return err
}
return nil
}
// Read reads a key within the transaction.
func (t *transaction) Read(ctx context.Context, path []string) (string, error) {
p := []string{"branch", t.fromBranch, "transactions", t.newBranch, "rw"}
for _, dir := range path[0 : len(path)-1] {
p = append(p, dir)
}
file, err := t.client.Open(ctx, p9p.OREAD, p...)
if err != nil {
return "", err
}
defer file.Close(ctx)
reader := file.NewIOReader(ctx, 0)
buf := bytes.NewBuffer(nil)
io.Copy(buf, reader)
return string(buf.Bytes()), nil
}
// Remove deletes a key within the transaction.
func (t *transaction) Remove(ctx context.Context, path []string) error {
p := []string{"branch", t.fromBranch, "transactions", t.newBranch, "rw"}
for _, dir := range path {
p = append(p, dir)
}
err := t.client.Remove(ctx, p...)
if err != nil {
log.Println("Failed to Remove ", p)
}
return nil
}

View File

@ -1,77 +0,0 @@
package datakit
import (
"io"
"log"
"strings"
p9p "github.com/docker/go-p9p"
"context"
)
type watch struct {
client *Client
file *File
offset int64 // current offset within head.live file
}
type Watch struct {
watch
}
// NewWatch starts watching a path within a branch
func NewWatch(ctx context.Context, client *Client, fromBranch string, path []string) (*Watch, error) {
// SHA=$(cat branch/<fromBranch>/watch/<path.node>/tree.live)
p := []string{"branch", fromBranch, "watch"}
for _, dir := range path {
p = append(p, dir+".node")
}
p = append(p, "tree.live")
file, err := client.Open(ctx, p9p.OREAD, p...)
if err != nil {
log.Println("Failed to open", p, err)
return nil, err
}
offset := int64(0)
return &Watch{watch{client: client, file: file, offset: offset}}, nil
}
func (w *Watch) Next(ctx context.Context) (*Snapshot, error) {
buf := make([]byte, 512)
sawFlush := false
for {
// NOTE: irmin9p-direct will never return a fragment;
// we can rely on the buffer containing a whold number
// of lines.
n, err := w.file.Read(ctx, buf, w.offset)
if n == 0 {
// Two reads of "" in a row means end-of-file
if sawFlush {
return nil, io.EOF
} else {
sawFlush = true
continue
}
} else {
sawFlush = false
}
w.offset = w.offset + int64(n)
if err != nil {
log.Println("Failed to Read head.live", err)
return nil, io.EOF
}
lines := strings.Split(string(buf[0:n]), "\n")
// Use the last non-empty line
thing := ""
for _, line := range lines {
if line != "" {
thing = line
}
}
return NewSnapshot(ctx, w.client, OBJECT, thing), nil
}
}
func (w *Watch) Close(ctx context.Context) {
w.file.Close(ctx)
}

View File

@ -14,21 +14,42 @@ VPNKit is a set of tools and services for helping [HyperKit](https://github.com/
VMs interoperate with host VPN configurations.
Building on Unix
----------------
Building on Unix (including Mac)
--------------------------------
First install `wget`, `opam` using your package manager of choice.
Build all the dependencies and the program itself with:
First install `wget`, `opam` and `pkg-config` using your package manager of choice.
If you are an existing `opam` user then you can either build against your existing `opam`
package universe, or the custom universe contained in this repo. To use the custom universe,
ensure that you unset your `OPAMROOT` environment variable:
```
cd [path to vpnkit source]
opam remote add vpnkit ./repo/darwin
opam install --deps-only vpnkit
unset OPAMROOT
```
To build, type
```
make
```
The first build will take a little longer as it will build all the package dependencies first.
When the build succeeds the `vpnkit.exe` binary should be available in the current directory.
Building on Windows
-------------------
First install the [OCaml environment with Cygwin](https://fdopen.github.io/opam-repository-mingw/installation/).
Note that although the Cygwin tools are needed for the build scripts, Cygwin itself will not
be linked to the final executable.
Inside the `OCaml64` (Cygwin) shell, unset the `OPAMROOT` environment and build by:
```
unset OPAMROOT
make
```
When the build succeeds the `vpnkit` binary should be available in the current path.
The first build will take a little longer as it will build all the package dependencies first.
When the build succeeds the `vpnkit.exe` binary should be available in the current directory.
Running with hyperkit
---------------------

View File

@ -2,7 +2,6 @@
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <syslog.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
@ -11,8 +10,11 @@
#include <fcntl.h>
#include <err.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <linux/vm_sockets.h>
#include "hvsock.h"
#include "log.h"
#define NONE 0
#define LISTEN 1
@ -20,15 +22,8 @@
int mode = NONE;
char *default_sid = "C378280D-DA14-42C8-A24E-0DE92A1028E2";
char *mount = "/bin/mount";
void fatal(const char *msg)
{
syslog(LOG_CRIT, "%s Error: %d. %s", msg, errno, strerror(errno));
exit(1);
}
static int handle(int fd, char *tag, char *path)
{
char *options = NULL;
@ -57,14 +52,13 @@ static int handle(int fd, char *tag, char *path)
res = waitpid(pid, &status, 0);
if (res == -1) {
syslog(LOG_CRIT,
"waitpid failed: %d. %s", errno, strerror(errno));
ERROR("waitpid failed: %d. %s", errno, strerror(errno));
exit(1);
}
return WEXITSTATUS(status);
}
static int create_listening_socket(GUID serviceid)
static int create_listening_hvsocket(GUID serviceid)
{
SOCKADDR_HV sa;
int lsock;
@ -72,8 +66,9 @@ static int create_listening_socket(GUID serviceid)
lsock = socket(AF_HYPERV, SOCK_STREAM, HV_PROTOCOL_RAW);
if (lsock == -1)
fatal("socket()");
return -1;
bzero(&sa, sizeof(sa));
sa.Family = AF_HYPERV;
sa.Reserved = 0;
sa.VmId = HV_GUID_WILDCARD;
@ -81,16 +76,44 @@ static int create_listening_socket(GUID serviceid)
res = bind(lsock, (const struct sockaddr *)&sa, sizeof(sa));
if (res == -1)
fatal("bind()");
return -1; /* ignore the fd leak */
res = listen(lsock, 1);
if (res == -1)
fatal("listen()");
return -1; /* ignore the fd leak */
return lsock;
}
static int connect_socket(GUID serviceid)
static int create_listening_vsocket(long port)
{
struct sockaddr_vm sa;
int lsock;
int res;
lsock = socket(AF_VSOCK, SOCK_STREAM, 0);
if (lsock == -1)
return -1;
bzero(&sa, sizeof(sa));
sa.svm_family = AF_VSOCK;
sa.svm_reserved1 = 0;
sa.svm_port = port;
sa.svm_cid = VMADDR_CID_ANY;
res = bind(lsock, (const struct sockaddr *)&sa, sizeof(sa));
if (res == -1)
return -1; /* ignore the fd leak */
res = listen(lsock, 1);
if (res == -1)
return -1; /* ignore the fd leak */
return lsock;
}
static int connect_hvsocket(GUID serviceid)
{
SOCKADDR_HV sa;
int sock;
@ -98,8 +121,9 @@ static int connect_socket(GUID serviceid)
sock = socket(AF_HYPERV, SOCK_STREAM, HV_PROTOCOL_RAW);
if (sock == -1)
fatal("socket()");
return -1;
bzero(&sa, sizeof(sa));
sa.Family = AF_HYPERV;
sa.Reserved = 0;
sa.VmId = HV_GUID_PARENT;
@ -107,12 +131,35 @@ static int connect_socket(GUID serviceid)
res = connect(sock, (const struct sockaddr *)&sa, sizeof(sa));
if (res == -1)
fatal("connect()");
return -1; /* ignore the fd leak */
return sock;
}
static int accept_socket(int lsock)
static int connect_vsocket(long port)
{
struct sockaddr_vm sa;
int sock;
int res;
sock = socket(AF_VSOCK, SOCK_STREAM, 0);
if (sock == -1)
return -1;
bzero(&sa, sizeof(sa));
sa.svm_family = AF_VSOCK;
sa.svm_reserved1 = 0;
sa.svm_port = port;
sa.svm_cid = VMADDR_CID_HOST;
res = connect(sock, (const struct sockaddr *)&sa, sizeof(sa));
if (res == -1)
return -1; /* ignore the fd leak */
return sock;
}
static int accept_hvsocket(int lsock)
{
SOCKADDR_HV sac;
socklen_t socklen = sizeof(sac);
@ -122,21 +169,35 @@ static int accept_socket(int lsock)
if (csock == -1)
fatal("accept()");
syslog(LOG_INFO, "Connect from: " GUID_FMT ":" GUID_FMT "\n",
INFO("Connect from: " GUID_FMT ":" GUID_FMT "\n",
GUID_ARGS(sac.VmId), GUID_ARGS(sac.ServiceId));
return csock;
}
static int accept_vsocket(int lsock)
{
struct sockaddr_vm sac;
socklen_t socklen = sizeof(sac);
int csock;
csock = accept(lsock, (struct sockaddr *)&sac, &socklen);
if (csock == -1)
fatal("accept()");
INFO("Connect from: port=%x cid=%d", sac.svm_port, sac.svm_cid);
return csock;
}
void usage(char *name)
{
printf("%s: mount a 9P filesystem from an hvsock connection\n", name);
printf("usage:\n");
printf("\t[--serviceid <guid>] <listen | connect> <tag> <path>\n");
printf("\t[--vsock port] <listen | connect> <tag> <path>\n");
printf("where\n");
printf("\t--serviceid <guid>: use <guid> as the well-known service GUID\n");
printf("\t (defaults to %s)\n", default_sid);
printf("\t--listen: listen forever for incoming AF_HVSOCK connections\n");
printf("\t--vsock <port>: use the AF_VSOCK <port>\n");
printf("\t--listen: listen forever for incoming AF_VSOCK connections\n");
printf("\t--connect: connect to the parent partition\n");
}
@ -145,8 +206,8 @@ int main(int argc, char **argv)
int res = 0;
GUID sid;
int c;
/* Defaults to a testing GUID */
char *serviceid = default_sid;
unsigned int port = 0;
char serviceid[37]; /* 36 for a GUID and 1 for a NULL */
char *tag = NULL;
char *path = NULL;
@ -154,18 +215,22 @@ int main(int argc, char **argv)
while (1) {
static struct option long_options[] = {
/* These options set a flag. */
{"serviceid", required_argument, NULL, 's'},
{"vsock", required_argument, NULL, 'v'},
{"verbose", no_argument, NULL, 'w'},
{0, 0, 0, 0}
};
int option_index = 0;
c = getopt_long(argc, argv, "s:", long_options, &option_index);
c = getopt_long(argc, argv, "v:", long_options, &option_index);
if (c == -1)
break;
switch (c) {
case 's':
serviceid = optarg;
case 'v':
port = (unsigned int) strtol(optarg, NULL, 0);
break;
case 'w':
verbose++;
break;
case 0:
break;
@ -206,6 +271,7 @@ int main(int argc, char **argv)
exit(1);
}
snprintf(serviceid, sizeof(serviceid), "%08x-FACB-11E6-BD58-64006A7986D3", port);
res = parseguid(serviceid, &sid);
if (res) {
fprintf(stderr,
@ -214,27 +280,39 @@ int main(int argc, char **argv)
exit(1);
}
openlog(argv[0], LOG_CONS | LOG_NDELAY | LOG_PERROR, LOG_DAEMON);
for (;;) {
int lsocket;
int sock;
int r;
if (mode == LISTEN) {
syslog(LOG_INFO, "starting in listening mode with serviceid=%s, tag=%s, path=%s", serviceid, tag, path);
lsocket = create_listening_socket(sid);
sock = accept_socket(lsocket);
close(lsocket);
INFO("starting in listening mode with port=%x, tag=%s, path=%s", port, tag, path);
lsocket = create_listening_vsocket(port);
if (lsocket != -1) {
sock = accept_vsocket(lsocket);
close(lsocket);
} else {
INFO("failed to create AF_VSOCK, trying with AF_HVSOCK serviceid=%s", serviceid);
lsocket = create_listening_hvsocket(sid);
if (lsocket == -1)
fatal("create_listening_vsocket");
sock = accept_hvsocket(lsocket);
close(lsocket);
}
} else {
syslog(LOG_INFO, "starting in connect mode with serviceid=%s, tag=%s, path=%s", serviceid, tag, path);
sock = connect_socket(sid);
INFO("starting in connect mode with port=%x, tag=%s, path=%s", port, tag, path);
sock = connect_vsocket(port);
if (sock == -1) {
INFO("failed to connect AF_VSOCK, trying with AF_HVSOCK serviceid=%s", serviceid);
sock = connect_hvsocket(sid);
}
}
r = handle(sock, tag, path);
close(sock);
if (r == 0) {
syslog(LOG_INFO, "mount successful for serviceid=%s tag=%s path=%s", serviceid, tag, path);
INFO("mount successful for (serviceid=%s) port=%x tag=%s path=%s", serviceid, port, tag, path);
exit(0);
}
@ -242,7 +320,7 @@ int main(int argc, char **argv)
* This can happen if the client times out the connection
* after we accept it
*/
syslog(LOG_CRIT, "mount failed with %d for serviceid=%s tag=%s path=%s", r, serviceid, tag, path);
ERROR("mount failed with %d for (serviceid=%s) port=%x tag=%s path=%s", r, serviceid, port, tag, path);
sleep(1); /* retry */
}
}

View File

@ -0,0 +1,13 @@
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include "log.h"
int verbose = 0;
void fatal(const char *msg)
{
ERROR("%s Error: %d. %s", msg, errno, strerror(errno));
exit(1);
}

View File

@ -0,0 +1,30 @@
#include <stdlib.h>
#include <stdio.h>
extern int verbose;
#define ERROR(...) \
do { \
fprintf(stderr, __VA_ARGS__); \
fprintf(stderr, "\n"); \
} while (0)
#define INFO(...) \
do { \
fprintf(stderr, __VA_ARGS__); \
fprintf(stderr, "\n"); \
} while (0)
#define DBG(...) \
do { \
if (verbose > 1) { \
fprintf(stderr, __VA_ARGS__); \
fprintf(stderr, "\n"); \
} \
} while (0)
#define TRC(...) \
do { \
if (verbose > 2) { \
fprintf(stderr, __VA_ARGS__); \
fprintf(stderr, "\n"); \
} \
} while (0)
extern void fatal(const char *msg);

View File

@ -1,40 +0,0 @@
#include <stdio.h>
#include "hvsock.h"
int parseguid(const char *s, GUID *g)
{
int res;
int p0, p1, p2, p3, p4, p5, p6, p7;
res = sscanf(s, GUID_FMT,
&g->Data1, &g->Data2, &g->Data3,
&p0, &p1, &p2, &p3, &p4, &p5, &p6, &p7);
if (res != 11)
return 1;
g->Data4[0] = p0;
g->Data4[1] = p1;
g->Data4[2] = p2;
g->Data4[3] = p3;
g->Data4[4] = p4;
g->Data4[5] = p5;
g->Data4[6] = p6;
g->Data4[7] = p7;
return 0;
}
DEFINE_GUID(HV_GUID_ZERO,
0x00000000, 0x0000, 0x0000, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
DEFINE_GUID(HV_GUID_BROADCAST,
0xFFFFFFFF, 0xFFFF, 0xFFFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF);
DEFINE_GUID(HV_GUID_WILDCARD,
0x00000000, 0x0000, 0x0000, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
DEFINE_GUID(HV_GUID_CHILDREN,
0x90db8b89, 0x0d35, 0x4f79, 0x8c, 0xe9, 0x49, 0xea, 0x0a, 0xc8, 0xb7, 0xcd);
DEFINE_GUID(HV_GUID_LOOPBACK,
0xe0e16197, 0xdd56, 0x4a10, 0x91, 0x95, 0x5e, 0xe7, 0xa1, 0x55, 0xa8, 0x38);
DEFINE_GUID(HV_GUID_PARENT,
0xa42e7cda, 0xd03f, 0x480c, 0x9c, 0xc2, 0xa4, 0xde, 0x20, 0xab, 0xb8, 0x78);

View File

@ -1,48 +0,0 @@
/* AF_HYPERV definitions and utilities */
#include <errno.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/socket.h>
/* GUID handling */
typedef struct _GUID {
uint32_t Data1;
uint16_t Data2;
uint16_t Data3;
uint8_t Data4[8];
} GUID;
#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
const GUID name = {l, w1, w2, {b1, b2, b3, b4, b5, b6, b7, b8}}
/* Helper macros for parsing/printing GUIDs */
#define GUID_FMT "%08x-%04hx-%04hx-%02x%02x-%02x%02x%02x%02x%02x%02x"
#define GUID_ARGS(_g) \
(_g).Data1, (_g).Data2, (_g).Data3, \
(_g).Data4[0], (_g).Data4[1], (_g).Data4[2], (_g).Data4[3], \
(_g).Data4[4], (_g).Data4[5], (_g).Data4[6], (_g).Data4[7]
#define GUID_SARGS(_g) \
&(_g).Data1, &(_g).Data2, &(_g).Data3, \
&(_g).Data4[0], &(_g).Data4[1], &(_g).Data4[2], &(_g).Data4[3], \
&(_g).Data4[4], &(_g).Data4[5], &(_g).Data4[6], &(_g).Data4[7]
extern int parseguid(const char *s, GUID *g);
/* HV Socket definitions */
#define AF_HYPERV 43
#define HV_PROTOCOL_RAW 1
typedef struct _SOCKADDR_HV {
unsigned short Family;
unsigned short Reserved;
GUID VmId;
GUID ServiceId;
} SOCKADDR_HV;
extern const GUID HV_GUID_ZERO;
extern const GUID HV_GUID_BROADCAST;
extern const GUID HV_GUID_WILDCARD;
extern const GUID HV_GUID_CHILDREN;
extern const GUID HV_GUID_LOOPBACK;
extern const GUID HV_GUID_PARENT;

View File

@ -0,0 +1,13 @@
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include "log.h"
int verbose = 0;
void fatal(const char *msg)
{
ERROR("%s Error: %d. %s", msg, errno, strerror(errno));
exit(1);
}

View File

@ -0,0 +1,30 @@
#include <stdlib.h>
#include <stdio.h>
extern int verbose;
#define ERROR(...) \
do { \
fprintf(stderr, __VA_ARGS__); \
fprintf(stderr, "\n"); \
} while (0)
#define INFO(...) \
do { \
fprintf(stderr, __VA_ARGS__); \
fprintf(stderr, "\n"); \
} while (0)
#define DBG(...) \
do { \
if (verbose > 1) { \
fprintf(stderr, __VA_ARGS__); \
fprintf(stderr, "\n"); \
} \
} while (0)
#define TRC(...) \
do { \
if (verbose > 2) { \
fprintf(stderr, __VA_ARGS__); \
fprintf(stderr, "\n"); \
} \
} while (0)
extern void fatal(const char *msg);

View File

@ -5,8 +5,8 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <syslog.h>
#include "log.h"
#include "protocol.h"
/* Version 0 of the protocol used this */
@ -23,13 +23,13 @@ int really_read(int fd, uint8_t *buffer, size_t total)
while (remaining > 0) {
n = read(fd, buffer, remaining);
if (n == 0) {
syslog(LOG_CRIT, "EOF reading from socket: closing\n");
ERROR("EOF reading from socket: closing\n");
goto err;
}
if (n < 0) {
syslog(LOG_CRIT,
"Failure reading from socket: closing: %s",
strerror(errno));
ERROR(
"Failure reading from socket: closing: %s",
strerror(errno));
goto err;
}
remaining -= (size_t) n;
@ -53,13 +53,13 @@ int really_write(int fd, uint8_t *buffer, size_t total)
while (remaining > 0) {
n = write(fd, buffer, remaining);
if (n == 0) {
syslog(LOG_CRIT, "EOF writing to socket: closing");
ERROR("EOF writing to socket: closing");
goto err;
}
if (n < 0) {
syslog(LOG_CRIT,
"Failure writing to socket: closing: %s",
strerror(errno));
ERROR(
"Failure writing to socket: closing: %s",
strerror(errno));
goto err;
}
remaining -= (size_t) n;
@ -117,7 +117,7 @@ int read_init_message(int fd, struct init_message *ci)
res = really_read(fd, (uint8_t *)&ci->hello[0], sizeof(ci->hello));
if (res == -1) {
syslog(LOG_CRIT, "Failed to read hello from client");
ERROR("Failed to read hello from client");
return -1;
}
@ -131,19 +131,19 @@ int read_init_message(int fd, struct init_message *ci)
res = memcmp(&ci->hello[0],
&expected_hello[0], sizeof(expected_hello));
if (res != 0) {
syslog(LOG_CRIT, "Failed to read header magic from client");
ERROR("Failed to read header magic from client");
return -1;
}
res = really_read(fd, (uint8_t *)&ci->version, sizeof(ci->version));
if (res == -1) {
syslog(LOG_CRIT, "Failed to read header version from client");
ERROR("Failed to read header version from client");
return -1;
}
res = really_read(fd, (uint8_t *)&ci->commit[0], sizeof(ci->commit));
if (res == -1) {
syslog(LOG_CRIT, "Failed to read header hash from client");
ERROR("Failed to read header hash from client");
return -1;
}
@ -156,21 +156,20 @@ int write_init_message(int fd, struct init_message *ci)
res = really_write(fd, (uint8_t *)&ci->hello[0], sizeof(ci->hello));
if (res == -1) {
syslog(LOG_CRIT, "Failed to write hello to client");
ERROR("Failed to write hello to client");
return -1;
}
if (ci->version > 0) {
res = really_write(fd, (uint8_t *)&ci->version,
sizeof(ci->version));
if (res == -1) {
syslog(LOG_CRIT, "Failed to write version to client");
ERROR("Failed to write version to client");
return -1;
}
res = really_write(fd, (uint8_t *)&ci->commit[0],
sizeof(ci->commit));
if (res == -1) {
syslog(LOG_CRIT,
"Failed to write header hash to client");
ERROR("Failed to write header hash to client");
return -1;
}
}
@ -182,7 +181,7 @@ int read_vif_response(int fd, struct vif_info *vif)
struct msg_response msg;
if (really_read(fd, (uint8_t*)&msg, sizeof(msg)) == -1) {
syslog(LOG_CRIT, "Client failed to read server response");
ERROR("Client failed to read server response");
return -1;
}
@ -191,10 +190,10 @@ int read_vif_response(int fd, struct vif_info *vif)
memcpy((uint8_t*)vif, (uint8_t*)&msg.vif, sizeof(*vif));
return 0;
case rt_disconnect:
syslog(LOG_CRIT, "Server disconnected: %*s", msg.disconnect.len, msg.disconnect.msg);
ERROR("Server disconnected: %*s", msg.disconnect.len, msg.disconnect.msg);
return -1;
default:
syslog(LOG_CRIT, "Unknown response type from server");
ERROR("Unknown response type from server");
return -1;
}
@ -205,7 +204,7 @@ int write_command(int fd, enum command *c)
uint8_t command = *c;
if (really_write(fd, (uint8_t *)&command, sizeof(command)) == -1) {
syslog(LOG_CRIT, "Failed to write command to client");
ERROR("Failed to write command to client");
return -1;
}
return 0;
@ -218,7 +217,7 @@ int write_ethernet_args(int fd, struct ethernet_args *args)
memcpy(&buffer[0], (uint8_t *)&args->uuid_string[0], 36);
if (really_write(fd, (uint8_t *)&buffer, sizeof(buffer)) == -1) {
syslog(LOG_CRIT, "Failed to write ethernet args to client");
ERROR("Failed to write ethernet args to client");
return -1;
}
return 0;

View File

@ -2,7 +2,6 @@
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <syslog.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
@ -23,50 +22,19 @@
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/un.h>
#include <ifaddrs.h>
#include "hvsock.h"
#include "protocol.h"
#include "ring.h"
#include "log.h"
int daemon_flag;
int nofork_flag;
int listen_flag;
int connect_flag;
char *default_sid = "30D48B34-7D27-4B0B-AAAF-BBBED334DD59";
/* Support big frames if the server requests it */
const int max_packet_size = 16384;
static int verbose;
#define INFO(...) \
do { \
if (verbose) { \
printf(__VA_ARGS__); \
fflush(stdout); \
} \
} while (0)
#define DBG(...) \
do { \
if (verbose > 1) { \
printf(__VA_ARGS__); \
fflush(stdout); \
} \
} while (0)
#define TRC(...) \
do { \
if (verbose > 2) { \
printf(__VA_ARGS__); \
fflush(stdout); \
} \
} while (0)
void fatal(const char *msg)
{
syslog(LOG_CRIT, "%s Error: %d. %s", msg, errno, strerror(errno));
exit(1);
}
int alloc_tap(const char *dev)
{
@ -88,7 +56,7 @@ int alloc_tap(const char *dev)
if (ioctl(fd, TUNSETPERSIST, persist) < 0)
fatal("TUNSETPERSIST failed");
syslog(LOG_INFO, "successfully created TAP device %s", dev);
INFO("successfully created TAP device %s", dev);
return fd;
}
@ -147,7 +115,7 @@ int negotiate(int fd, struct vif_info *vif)
goto err;
if (me->version != you.version) {
syslog(LOG_CRIT, "Server did not accept our protocol version (client: %d, server: %d)", me->version, you.version);
ERROR("Server did not accept our protocol version (client: %d, server: %d)", me->version, you.version);
goto err;
}
@ -155,7 +123,7 @@ int negotiate(int fd, struct vif_info *vif)
if (!txt)
goto err;
syslog(LOG_INFO, "Server reports %s", txt);
INFO("Server reports %s", txt);
free(txt);
if (write_command(fd, &command) == -1)
@ -171,7 +139,7 @@ int negotiate(int fd, struct vif_info *vif)
return 0;
err:
syslog(LOG_CRIT, "Failed to negotiate vmnet connection");
ERROR("Failed to negotiate vmnet connection");
return 1;
}
@ -231,14 +199,14 @@ static void* vmnet_to_ring(void *arg)
ssize_t n = readv(c->fd, &iovec[0], iovec_len);
TRC("vmnet_to_ring: read %zd\n", n);
if (n == 0) {
syslog(LOG_CRIT, "EOF reading from socket: closing\n");
ERROR("EOF reading from socket: closing\n");
ring_producer_eof(ring);
goto err;
}
if (n < 0) {
syslog(LOG_CRIT,
"Failure reading from socket: closing: %s (%d)",
strerror(errno), errno);
ERROR(
"Failure reading from socket: closing: %s (%d)",
strerror(errno), errno);
ring_producer_eof(ring);
goto err;
}
@ -280,9 +248,9 @@ static void* ring_to_tap(void *arg)
assert(length > 0);
TRC("ring_to_tap: packet of length %d\n", length);
if (length > max_packet_size) {
syslog(LOG_CRIT,
"Received an over-large packet: %d > %ld",
length, max_packet_size);
ERROR(
"Received an over-large packet: %d > %d",
length, max_packet_size);
exit(1);
}
ring_consumer_advance(ring, 2);
@ -297,8 +265,7 @@ static void* ring_to_tap(void *arg)
trim_iovec(iovec, &iovec_len, length);
ssize_t n = writev(c->tapfd, &iovec[0], iovec_len);
if (n != length) {
syslog(LOG_CRIT,
"Failed to write %d bytes to tap device (wrote %d)", length, n);
ERROR("Failed to write %d bytes to tap device (wrote %zd)", length, n);
//exit(1);
}
TRC("ring_to_tap: ring_consumer_advance n=%zd\n", n);
@ -354,7 +321,7 @@ static void *tap_to_ring(void *arg)
if (errno == ENXIO)
fatal("tap device has gone down");
syslog(LOG_WARNING, "ignoring error %d", errno);
INFO("ignoring error %d", errno);
/*
* This is what mirage-net-unix does. Is it a good
* idea really?
@ -431,70 +398,29 @@ static void handle(struct connection *connection)
fatal("Failed to join the ring_to_vmnet thread");
}
static int create_listening_socket(GUID serviceid)
static int connect_unix_socket(char *path)
{
SOCKADDR_HV sa;
int lsock;
int res;
lsock = socket(AF_HYPERV, SOCK_STREAM, HV_PROTOCOL_RAW);
if (lsock == -1)
fatal("socket()");
sa.Family = AF_HYPERV;
sa.Reserved = 0;
sa.VmId = HV_GUID_WILDCARD;
sa.ServiceId = serviceid;
res = bind(lsock, (const struct sockaddr *)&sa, sizeof(sa));
if (res == -1)
fatal("bind()");
res = listen(lsock, SOMAXCONN);
if (res == -1)
fatal("listen()");
return lsock;
}
static int connect_socket(GUID serviceid)
{
SOCKADDR_HV sa;
struct sockaddr_un sa;
int sock;
int res;
sock = socket(AF_HYPERV, SOCK_STREAM, HV_PROTOCOL_RAW);
sock = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock == -1)
fatal("socket()");
return -1;
sa.Family = AF_HYPERV;
sa.Reserved = 0;
sa.VmId = HV_GUID_PARENT;
sa.ServiceId = serviceid;
bzero(&sa, sizeof(sa));
sa.sun_family = AF_UNIX;
strncpy(sa.sun_path, path, sizeof(sa.sun_path)-1);
res = connect(sock, (const struct sockaddr *)&sa, sizeof(sa));
if (res == -1)
fatal("connect()");
if (res == -1) {
close(sock);
return -1;
}
return sock;
}
static int accept_socket(int lsock)
{
SOCKADDR_HV sac;
socklen_t socklen = sizeof(sac);
int csock;
csock = accept(lsock, (struct sockaddr *)&sac, &socklen);
if (csock == -1)
fatal("accept()");
syslog(LOG_INFO, "Connect from: " GUID_FMT ":" GUID_FMT "\n",
GUID_ARGS(sac.VmId), GUID_ARGS(sac.ServiceId));
return csock;
}
void write_pidfile(const char *pidfile)
{
pid_t pid = getpid();
@ -508,7 +434,7 @@ void write_pidfile(const char *pidfile)
len = strlen(pid_s);
file = fopen(pidfile, "w");
if (file == NULL) {
syslog(LOG_CRIT, "Failed to open pidfile %s", pidfile);
ERROR("Failed to open pidfile %s", pidfile);
exit(1);
}
@ -551,53 +477,46 @@ void daemonize(const char *pidfile)
void usage(char *name)
{
printf("%s usage:\n", name);
printf("\t[--daemon] [--tap <name>] [--serviceid <guid>] [--pid <file>]\n");
printf("\t[--message-size <bytes>] [--buffer-size <bytes>]\n");
printf("\t[--listen | --connect]\n\n");
printf("\t[--daemon] [--tap <name>] [--path <socket>] [--pid <file>]\n");
printf("\t[--message-size <bytes>] [--buffer-size <bytes>]\n\n");
printf("where\n");
printf("\t--daemonize: run as a background daemon\n");
printf("\t--daemon: run as a background daemon\n");
printf("\t--nofork: don't run handlers in subprocesses\n");
printf("\t--tap <name>: create a tap device with the given name\n");
printf("\t (defaults to eth1)\n");
printf("\t--serviceid <guid>: use <guid> as the well-known service GUID\n");
printf("\t (defaults to %s)\n", default_sid);
printf("\t--path <socket>: use <socket> as the path to the vpnkit host server\n");
printf("\t--pid <file>: write a pid to the given file\n");
printf("\t--message-size <bytes>: dictates the maximum transfer size for AF_HVSOCK\n");
printf("\t--buffer-size <bytes>: dictates the buffer size for AF_HVSOCK\n");
printf("\t--listen: listen forever for incoming AF_HVSOCK connections\n");
printf("\t--connect: connect to the parent partition\n");
}
int main(int argc, char **argv)
{
char *serviceid = default_sid;
char *path = NULL;
struct connection connection;
char *tap = "eth1";
char *pidfile = NULL;
int lsocket = -1;
char *post_up_script = NULL;
int sock = -1;
int res = 0;
int status;
pid_t child;
int tapfd;
int ring_size = 1048576;
int message_size = 8192; /* Well known to work across Hyper-V versions */
GUID sid;
int c;
int option_index;
int log_flags = LOG_CONS | LOG_NDELAY;
static struct option long_options[] = {
/* These options set a flag. */
{"daemon", no_argument, &daemon_flag, 1},
{"nofork", no_argument, &nofork_flag, 1},
{"serviceid", required_argument, NULL, 's'},
{"path", required_argument, NULL, 'w'},
{"tap", required_argument, NULL, 't'},
{"pidfile", required_argument, NULL, 'p'},
{"listen", no_argument, &listen_flag, 1},
{"connect", no_argument, &connect_flag, 1},
{"post-up-script", required_argument, NULL, 'x'},
{"buffer-size", required_argument, NULL, 'b'},
{"message-size", required_argument, NULL, 'm'},
{"verbose", no_argument, NULL, 'v'},
{0, 0, 0, 0}
};
@ -605,7 +524,7 @@ int main(int argc, char **argv)
while (1) {
option_index = 0;
c = getopt_long(argc, argv, "ds:t:p:r:m:v",
c = getopt_long(argc, argv, "dw:t:p:r:m:v",
long_options, &option_index);
if (c == -1)
break;
@ -617,8 +536,8 @@ int main(int argc, char **argv)
case 'n':
nofork_flag = 1;
break;
case 's':
serviceid = optarg;
case 'w':
path = optarg;
break;
case 't':
tap = optarg;
@ -626,6 +545,9 @@ int main(int argc, char **argv)
case 'p':
pidfile = optarg;
break;
case 'x':
post_up_script = optarg;
break;
case 'b':
ring_size = atoi(optarg);
break;
@ -643,39 +565,19 @@ int main(int argc, char **argv)
}
}
if ((listen_flag && connect_flag) || !(listen_flag || connect_flag)) {
fprintf(stderr, "Please supply either the --listen or --connect flag, but not both.\n");
exit(1);
}
if (daemon_flag && !pidfile) {
fprintf(stderr, "For daemon mode, please supply a --pidfile argument.\n");
exit(1);
}
res = parseguid(serviceid, &sid);
if (res) {
fprintf(stderr, "Failed to parse serviceid as GUID: %s\n", serviceid);
usage(argv[0]);
exit(1);
}
if (!daemon_flag)
log_flags |= LOG_PERROR;
openlog(argv[0], log_flags, LOG_DAEMON);
tapfd = alloc_tap(tap);
connection.tapfd = tapfd;
connection.to_vmnet_ring = ring_allocate(ring_size);
connection.from_vmnet_ring = ring_allocate(ring_size);
connection.message_size = message_size;
if (listen_flag) {
syslog(LOG_INFO, "starting in listening mode with serviceid=%s and tap=%s", serviceid, tap);
lsocket = create_listening_socket(sid);
} else {
syslog(LOG_INFO, "starting in connect mode with serviceid=%s and tap=%s", serviceid, tap);
}
INFO("starting in connect mode with path=%s and tap=%s", path, tap);
for (;;) {
if (sock != -1) {
@ -683,10 +585,11 @@ int main(int argc, char **argv)
sock = -1;
}
if (listen_flag)
sock = accept_socket(lsocket);
else
sock = connect_socket(sid);
sock = connect_unix_socket(path);
if (sock == -1){
sleep(0.1);
continue;
}
connection.fd = sock;
if (negotiate(sock, &connection.vif) != 0) {
@ -694,13 +597,19 @@ int main(int argc, char **argv)
continue;
}
syslog(LOG_INFO, "VMNET VIF has MAC %02x:%02x:%02x:%02x:%02x:%02x",
INFO("VMNET VIF has MAC %02x:%02x:%02x:%02x:%02x:%02x",
connection.vif.mac[0], connection.vif.mac[1], connection.vif.mac[2],
connection.vif.mac[3], connection.vif.mac[4], connection.vif.mac[5]
);
set_macaddr(tap, &connection.vif.mac[0]);
set_mtu(tap, connection.vif.mtu);
if (post_up_script) {
INFO("Executing post-up-script %s", post_up_script);
int result = system(post_up_script);
INFO("Result of post-up-script = %d", result);
}
/* Daemonize after we've made our first reliable connection */
if (daemon_flag) {
daemon_flag = 0;

View File

@ -0,0 +1,51 @@
package libproxy
import (
"errors"
"net"
"os"
"strings"
)
// ExposePort exposes a port using 9p
func ExposePort(host net.Addr, container net.Addr) (*os.File, error) {
name := host.Network() + ":" + host.String() + ":" + container.Network() + ":" + container.String()
log.Printf("exposePort %s\n", name)
err := os.Mkdir("/port/"+name, 0)
if err != nil {
log.Printf("Failed to mkdir /port/%s: %#v\n", name, err)
return nil, err
}
ctl, err := os.OpenFile("/port/"+name+"/ctl", os.O_RDWR, 0)
if err != nil {
log.Printf("Failed to open /port/%s/ctl: %#v\n", name, err)
return nil, err
}
_, err = ctl.WriteString(name)
if err != nil {
log.Printf("Failed to open /port/%s/ctl: %#v\n", name, err)
return nil, err
}
_, err = ctl.Seek(0, 0)
if err != nil {
log.Printf("Failed to seek on /port/%s/ctl: %#v\n", name, err)
return nil, err
}
results := make([]byte, 100)
count, err := ctl.Read(results)
if err != nil {
log.Printf("Failed to read from /port/%s/ctl: %#v\n", name, err)
return nil, err
}
// We deliberately keep the control file open since 9P clunk
// will trigger a shutdown on the host side.
response := string(results[0:count])
if strings.HasPrefix(response, "ERROR ") {
os.Remove("/port/" + name + "/ctl")
response = strings.Trim(response[6:], " \t\r\n")
return nil, errors.New(response)
}
// Hold on to a reference to prevent premature GC and close
return ctl, nil
}

View File

@ -0,0 +1,77 @@
package libproxy
import (
"fmt"
"net"
)
// Forward a connection to a given destination.
func Forward(conn Conn, destination Destination, quit <-chan struct{}) {
defer conn.Close()
switch destination.Proto {
case TCP:
backendAddr := net.TCPAddr{IP: destination.IP, Port: int(destination.Port), Zone: ""}
if err := HandleTCPConnection(conn, &backendAddr, quit); err != nil {
log.Printf("closing TCP proxy because %v", err)
return
}
case Unix:
backendAddr, err := net.ResolveUnixAddr("unix", destination.Path)
if err != nil {
log.Printf("Error resolving Unix address %s", destination.Path)
return
}
if err := HandleUnixConnection(conn, backendAddr, quit); err != nil {
log.Printf("closing Unix proxy because %v", err)
return
}
case UDP:
backendAddr := &net.UDPAddr{IP: destination.IP, Port: int(destination.Port), Zone: ""}
// copy to and from the backend without using NewUDPProxy
inside, err := net.DialUDP("udp", nil, backendAddr)
if err != nil {
log.Printf("Failed to Dial UDP backend for %s: %v", backendAddr, err)
return
}
log.Printf("accepted UDP connection to %s\n", backendAddr.String())
one := make(chan struct{})
two := make(chan struct{})
go func() {
copyUDP(fmt.Sprintf("from %s to host", backendAddr.String()), inside, conn)
close(one)
}()
go func() {
copyUDP(fmt.Sprintf("from host to %s", backendAddr.String()), conn, inside)
close(two)
}()
select {
case <-quit: // we want to quit
case <-one: // we get an error like "connection refused"
case <-two: // we get an error like "connection refused"
}
log.Printf("closing UDP connection to %s\n", backendAddr.String())
_ = inside.Close()
return
default:
log.Printf("Unknown protocol: %d", destination.Proto)
return
}
}
func copyUDP(description string, left, right net.Conn) {
b := make([]byte, UDPBufSize)
for {
n, err := left.Read(b)
if err != nil {
log.Printf("%s: unable to read UDP: %v", description, err)
return
}
pkt := b[0:n]
_, err = right.Write(pkt)
if err != nil {
log.Printf("%s: unable to write UDP: %v", description, err)
return
}
}
}

View File

@ -0,0 +1,443 @@
package libproxy
import (
"encoding/binary"
"errors"
"fmt"
"io"
"net"
)
// Proto is the protocol of the flow
type Proto uint8
const (
// TCP flow
TCP Proto = 1
// UDP flow
UDP Proto = 2
// Unix domain socket flow
Unix Proto = 3
)
// Destination refers to a listening TCP or UDP service
type Destination struct {
Proto Proto
IP net.IP
Port uint16
Path string
}
func (d Destination) String() string {
switch d.Proto {
case TCP:
return fmt.Sprintf("TCP:%s:%d", d.IP.String(), d.Port)
case UDP:
return fmt.Sprintf("UDP:%s:%d", d.IP.String(), d.Port)
case Unix:
return fmt.Sprintf("Unix:%s", d.Path)
}
return "Unknown"
}
// Read header which describes TCP/UDP and destination IP:port
func unmarshalDestination(r io.Reader) (Destination, error) {
d := Destination{}
if err := binary.Read(r, binary.LittleEndian, &d.Proto); err != nil {
return d, err
}
switch d.Proto {
case TCP, UDP:
var length uint16
// IP length
if err := binary.Read(r, binary.LittleEndian, &length); err != nil {
return d, err
}
d.IP = make([]byte, length)
if err := binary.Read(r, binary.LittleEndian, &d.IP); err != nil {
return d, err
}
if err := binary.Read(r, binary.LittleEndian, &d.Port); err != nil {
return d, err
}
case Unix:
var length uint16
// String length
if err := binary.Read(r, binary.LittleEndian, &length); err != nil {
return d, err
}
path := make([]byte, length)
if err := binary.Read(r, binary.LittleEndian, &path); err != nil {
return d, err
}
d.Path = string(path)
}
return d, nil
}
func (d Destination) Write(w io.Writer) error {
if err := binary.Write(w, binary.LittleEndian, d.Proto); err != nil {
return err
}
switch d.Proto {
case TCP, UDP:
b := []byte(d.IP)
length := uint16(len(b))
if err := binary.Write(w, binary.LittleEndian, length); err != nil {
return err
}
if err := binary.Write(w, binary.LittleEndian, b); err != nil {
return err
}
if err := binary.Write(w, binary.LittleEndian, d.Port); err != nil {
return err
}
case Unix:
b := []byte(d.Path)
length := uint16(len(b))
if err := binary.Write(w, binary.LittleEndian, length); err != nil {
return err
}
if err := binary.Write(w, binary.LittleEndian, b); err != nil {
return err
}
}
return nil
}
// Size returns the marshalled size in bytes
func (d Destination) Size() int {
switch d.Proto {
case TCP, UDP:
return 1 + 2 + len(d.IP) + 2
case Unix:
return 1 + 2 + len(d.Path)
}
return 0
}
// Connection indicates whether the connection will use multiplexing or not.
type Connection int8
func (c Connection) String() string {
switch c {
case Dedicated:
return "Dedicated"
case Multiplexed:
return "Multiplexed"
default:
return "Unknown"
}
}
const (
// Dedicated means this connection will not use multiplexing
Dedicated Connection = iota + 1
// Multiplexed means this connection will contain labelled sub-connections mixed together
Multiplexed
)
// OpenFrame requests to connect to a proxy backend
type OpenFrame struct {
Connection Connection // Connection describes whether the opened connection should be dedicated or multiplexed
Destination Destination
}
func unmarshalOpen(r io.Reader) (*OpenFrame, error) {
o := &OpenFrame{}
if err := binary.Read(r, binary.LittleEndian, &o.Connection); err != nil {
return nil, err
}
d, err := unmarshalDestination(r)
if err != nil {
return nil, err
}
o.Destination = d
return o, nil
}
func (o *OpenFrame) Write(w io.Writer) error {
if err := binary.Write(w, binary.LittleEndian, o.Connection); err != nil {
return err
}
return o.Destination.Write(w)
}
// Size returns the marshalled size of the Open message
func (o *OpenFrame) Size() int {
return 1 + o.Destination.Size()
}
// CloseFrame requests to disconnect from a proxy backend
type CloseFrame struct {
}
// ShutdownFrame requests to close the write channel to a proxy backend
type ShutdownFrame struct {
}
// DataFrame is the header of a frame containing user data
type DataFrame struct {
payloadlen uint32
}
func unmarshalData(r io.Reader) (*DataFrame, error) {
d := &DataFrame{}
err := binary.Read(r, binary.LittleEndian, &d.payloadlen)
return d, err
}
func (d *DataFrame) Write(w io.Writer) error {
return binary.Write(w, binary.LittleEndian, d.payloadlen)
}
// Size returns the marshalled size of the data payload header
func (d *DataFrame) Size() int {
return 4
}
// WindowFrame is a window advertisement message
type WindowFrame struct {
seq uint64
}
func unmarshalWindow(r io.Reader) (*WindowFrame, error) {
w := &WindowFrame{}
err := binary.Read(r, binary.LittleEndian, &w.seq)
return w, err
}
func (win *WindowFrame) Write(w io.Writer) error {
return binary.Write(w, binary.LittleEndian, win.seq)
}
// Size returned the marshalled size of the Window payload
func (win *WindowFrame) Size() int {
return 8
}
// Command is the action requested by a message.
type Command int8
const (
// Open requests to open a connection to a backend service.
Open Command = iota + 1
// Close requests and then acknowledges the close of a sub-connection
Close
// Shutdown indicates that no more data will be written in this direction
Shutdown
// Data is a payload of a connection/sub-connection
Data
// Window is permission to send and consume buffer space
Window
)
// Frame is the low-level message sent to the multiplexer
type Frame struct {
Command Command // Command is the action erquested
ID uint32 // Id of the sub-connection, managed by the client
open *OpenFrame
close *CloseFrame
shutdown *ShutdownFrame
window *WindowFrame
data *DataFrame
}
func unmarshalFrame(r io.Reader) (*Frame, error) {
f := &Frame{}
var totallen uint16
if err := binary.Read(r, binary.LittleEndian, &totallen); err != nil {
return nil, err
}
if err := binary.Read(r, binary.LittleEndian, &f.Command); err != nil {
return nil, err
}
if err := binary.Read(r, binary.LittleEndian, &f.ID); err != nil {
return nil, err
}
switch f.Command {
case Open:
o, err := unmarshalOpen(r)
if err != nil {
return nil, err
}
f.open = o
case Close:
// no payload
case Shutdown:
// no payload
case Window:
w, err := unmarshalWindow(r)
if err != nil {
return nil, err
}
f.window = w
case Data:
d, err := unmarshalData(r)
if err != nil {
return nil, err
}
f.data = d
}
return f, nil
}
func (f *Frame) Write(w io.Writer) error {
frameLen := uint16(f.Size())
if err := binary.Write(w, binary.LittleEndian, frameLen); err != nil {
return err
}
if err := binary.Write(w, binary.LittleEndian, f.Command); err != nil {
return err
}
if err := binary.Write(w, binary.LittleEndian, f.ID); err != nil {
return err
}
switch f.Command {
case Open:
if err := f.open.Write(w); err != nil {
return err
}
case Close:
// no payload
case Shutdown:
// no payload
case Window:
if err := f.window.Write(w); err != nil {
return err
}
case Data:
if err := f.data.Write(w); err != nil {
return err
}
}
return nil
}
// Size returns the marshalled size of the frame
func (f *Frame) Size() int {
// include 2 for the preceeding length field
len := 2 + 1 + 4
switch f.Command {
case Open:
len = len + f.open.Size()
case Close:
// no payload
case Shutdown:
// no payload
case Window:
len = len + f.window.Size()
case Data:
len = len + f.data.Size()
}
return len
}
func (f *Frame) String() string {
switch f.Command {
case Open:
return fmt.Sprintf("%d Open %s %s", f.ID, f.open.Connection.String(), f.open.Destination.String())
case Close:
return fmt.Sprintf("%d Close", f.ID)
case Shutdown:
return fmt.Sprintf("%d Shutdown", f.ID)
case Window:
return fmt.Sprintf("%d Window %d", f.ID, f.window.seq)
case Data:
return fmt.Sprintf("%d Data length %d", f.ID, f.data.payloadlen)
default:
return "unknown"
}
}
// Window returns the payload of the frame, if it has Command = Window.
func (f *Frame) Window() (*WindowFrame, error) {
if f.Command != Window {
return nil, errors.New("Frame is not a Window()")
}
return f.window, nil
}
// Open returns the payload of the frame, if it has Command = Open
func (f *Frame) Open() (*OpenFrame, error) {
if f.Command != Open {
return nil, errors.New("Frame is not an Open()")
}
return f.open, nil
}
// Data returns the payload of the frame, if it has Command = Data
func (f *Frame) Data() (*DataFrame, error) {
if f.Command != Data {
return nil, errors.New("Frame is not Data()")
}
return f.data, nil
}
// Payload returns the payload of the frame.
func (f *Frame) Payload() interface{} {
switch f.Command {
case Open:
return f.open
case Close:
return f.close
case Shutdown:
return f.shutdown
case Window:
return f.window
case Data:
return f.data
default:
return nil
}
}
// NewWindow creates a Window message
func NewWindow(ID uint32, seq uint64) *Frame {
return &Frame{
Command: Window,
ID: ID,
window: &WindowFrame{
seq: seq,
},
}
}
// NewOpen creates an open message
func NewOpen(ID uint32, d Destination) *Frame {
return &Frame{
Command: Open,
ID: ID,
open: &OpenFrame{
Connection: Multiplexed,
Destination: d,
},
}
}
// NewData creates a data header frame
func NewData(ID, payloadlen uint32) *Frame {
return &Frame{
Command: Data,
ID: ID,
data: &DataFrame{
payloadlen: payloadlen,
},
}
}
// NewShutdown creates a shutdown frame
func NewShutdown(ID uint32) *Frame {
return &Frame{
Command: Shutdown,
ID: ID,
}
}
// NewClose creates a close frame
func NewClose(ID uint32) *Frame {
return &Frame{
Command: Close,
ID: ID,
}
}

View File

@ -0,0 +1,50 @@
package libproxy
import (
"encoding/binary"
"fmt"
"io"
)
type handshake struct {
// In future we could add flags here for feature negotiation.
// The length is dynamic but it must be < 64k.
payload []byte // uninterpreted
}
const handshakeMagic = "https://github.com/moby/vpnkit multiplexer protocol\n"
func unmarshalHandshake(r io.Reader) (*handshake, error) {
magic := make([]byte, len(handshakeMagic))
if err := binary.Read(r, binary.LittleEndian, &magic); err != nil {
return nil, err
}
if string(magic) != handshakeMagic {
return nil, fmt.Errorf("not a connection to a mulitplexer; received bad magic string '%s'", string(magic))
}
var length uint16
if err := binary.Read(r, binary.LittleEndian, &length); err != nil {
// it will be common to fail here with io.EOF
return nil, err
}
payload := make([]byte, length)
if err := binary.Read(r, binary.LittleEndian, &payload); err != nil {
return nil, err
}
return &handshake{
payload: payload,
}, nil
}
func (h *handshake) Write(w io.Writer) error {
magic := []byte(handshakeMagic)
if err := binary.Write(w, binary.LittleEndian, magic); err != nil {
return err
}
length := uint16(len(h.payload))
if err := binary.Write(w, binary.LittleEndian, length); err != nil {
return err
}
return binary.Write(w, binary.LittleEndian, h.payload)
}

View File

@ -0,0 +1,12 @@
package libproxy
import (
"github.com/sirupsen/logrus"
)
var log = logrus.New()
// SetLogger sets a new default logger
func SetLogger(l *logrus.Logger) {
log = l
}

View File

@ -0,0 +1,226 @@
package libproxy
import (
"io"
"net"
"sync"
"time"
)
// io.Pipe is synchronous but we need to decouple the Read and Write calls
// with buffering. Adding bufio.NewWriter still requires domeone else to call
// `Flush` in a background thread to perform the write. It's simpler to create
// our own bufferedPipe out of an array of []byte
// - each direction within the connection is represented by a bufferedPipe
// - each bufferedPipe can be shutdown such that further writes return EOF
// and reads return EOF after the buffer is exhausted
type bufferedPipe struct {
bufs [][]byte
eof bool
m *sync.Mutex
c *sync.Cond
readDeadline time.Time
}
func newBufferedPipe() *bufferedPipe {
var m sync.Mutex
c := sync.NewCond(&m)
return &bufferedPipe{
m: &m,
c: c,
}
}
func (pipe *bufferedPipe) TryReadLocked(p []byte) (n int, err error) {
// drain buffers before considering EOF
if len(pipe.bufs) > 0 {
n := copy(p, pipe.bufs[0])
pipe.bufs[0] = pipe.bufs[0][n:]
if len(pipe.bufs[0]) == 0 {
// first fragment consumed
pipe.bufs = pipe.bufs[1:]
}
return n, nil
}
if pipe.eof {
return 0, io.EOF
}
return 0, nil
}
func (pipe *bufferedPipe) SetReadDeadline(deadline time.Time) error {
pipe.m.Lock()
defer pipe.m.Unlock()
pipe.readDeadline = deadline
pipe.c.Broadcast()
return nil
}
type errTimeout struct {
}
func (e *errTimeout) String() string {
return "i/o timeout"
}
func (e *errTimeout) Error() string {
return e.String()
}
func (e *errTimeout) Timeout() bool {
return true
}
func (e *errTimeout) Temporary() bool {
return true
}
func (pipe *bufferedPipe) Read(p []byte) (n int, err error) {
pipe.m.Lock()
defer pipe.m.Unlock()
for {
n, err := pipe.TryReadLocked(p)
if n > 0 || err != nil {
return n, err
}
done := make(chan struct{})
timeout := make(chan time.Time)
if !pipe.readDeadline.IsZero() {
go func() {
time.Sleep(time.Until(pipe.readDeadline))
close(timeout)
}()
}
go func() {
pipe.c.Wait()
close(done)
}()
select {
case <-timeout:
// Clean up the goroutine
pipe.c.Broadcast()
<-done
return n, &errTimeout{}
case <-done:
// The timeout will still fire in the background
continue
}
}
}
func (pipe *bufferedPipe) Write(p []byte) (n int, err error) {
buf := make([]byte, len(p))
copy(buf, p)
pipe.m.Lock()
defer pipe.m.Unlock()
if pipe.eof {
return 0, io.EOF
}
if len(p) == 0 {
return 0, nil
}
pipe.bufs = append(pipe.bufs, buf)
pipe.c.Broadcast()
return len(p), nil
}
func (pipe *bufferedPipe) closeWriteNoErr() {
pipe.m.Lock()
defer pipe.m.Unlock()
pipe.eof = true
pipe.c.Broadcast()
}
func (pipe *bufferedPipe) CloseWrite() error {
pipe.closeWriteNoErr()
return nil
}
type loopback struct {
write *bufferedPipe
read *bufferedPipe
simulateLatency time.Duration
}
func newLoopback() *loopback {
write := newBufferedPipe()
read := newBufferedPipe()
return &loopback{
write: write,
read: read,
}
}
func (l *loopback) LocalAddr() net.Addr {
return &addrLoopback{}
}
func (l *loopback) RemoteAddr() net.Addr {
return &addrLoopback{}
}
type addrLoopback struct {
}
func (a *addrLoopback) Network() string {
return "loopback"
}
func (a *addrLoopback) String() string {
return ""
}
func (l *loopback) SetReadDeadline(timeout time.Time) error {
return l.read.SetReadDeadline(timeout)
}
func (l *loopback) SetWriteDeadline(_ time.Time) error {
// Writes never block
return nil
}
func (l *loopback) SetDeadline(timeout time.Time) error {
if err := l.SetReadDeadline(timeout); err != nil {
return err
}
return l.SetWriteDeadline(timeout)
}
func (l *loopback) OtherEnd() *loopback {
return &loopback{
write: l.read,
read: l.write,
}
}
func (l *loopback) Read(p []byte) (n int, err error) {
return l.read.Read(p)
}
func (l *loopback) Write(p []byte) (n int, err error) {
n, err = l.write.Write(p)
time.Sleep(l.simulateLatency)
return
}
func (l *loopback) CloseRead() error {
return l.read.CloseWrite()
}
func (l *loopback) CloseWrite() error {
return l.write.CloseWrite()
}
func (l *loopback) Close() error {
err1 := l.CloseRead()
err2 := l.CloseWrite()
if err1 != nil {
return err1
}
return err2
}
var _ Conn = &loopback{}

View File

@ -0,0 +1,635 @@
package libproxy
import (
"bufio"
"bytes"
"container/ring"
"errors"
"fmt"
"io"
"net"
"sync"
"time"
"golang.org/x/sync/errgroup"
)
const (
maxBufferSize = 65536
)
type windowState struct {
current uint64
allowed uint64
}
func (w *windowState) String() string {
return fmt.Sprintf("current %d, allowed %d", w.current, w.allowed)
}
func (w *windowState) size() int {
return int(w.allowed - w.current)
}
func (w *windowState) isAlmostClosed() bool {
return w.size() < maxBufferSize/2
}
func (w *windowState) advance() {
w.allowed = w.current + uint64(maxBufferSize)
}
type channel struct {
m *sync.Mutex
c *sync.Cond
multiplexer *multiplexer
destination Destination
ID uint32
read *windowState
write *windowState
readPipe *bufferedPipe
closeReceived bool
closeSent bool
// initially 2 (sender + receiver), protected by the multiplexer
refCount int
shutdownSent bool
writeDeadline time.Time
testAllowDataAfterCloseWrite bool
}
func (c *channel) String() string {
c.m.Lock()
defer c.m.Unlock()
closeReceived := ""
if c.closeReceived {
closeReceived = "closeReceived "
}
closeSent := ""
if c.closeSent {
closeSent = "closeSent "
}
shutdownSent := ""
if c.shutdownSent {
shutdownSent = "shutdownSent "
}
return fmt.Sprintf("ID %d -> %s %s%s%s", c.ID, c.destination.String(), closeReceived, closeSent, shutdownSent)
}
// newChannel registers a channel through the multiplexer
func newChannel(multiplexer *multiplexer, ID uint32, d Destination) *channel {
var m sync.Mutex
c := sync.NewCond(&m)
readPipe := newBufferedPipe()
return &channel{
m: &m,
c: c,
multiplexer: multiplexer,
destination: d,
ID: ID,
read: &windowState{},
write: &windowState{},
readPipe: readPipe,
refCount: 2,
}
}
func (c *channel) sendWindowUpdate() error {
c.m.Lock()
c.read.advance()
seq := c.read.allowed
c.m.Unlock()
return c.multiplexer.send(NewWindow(c.ID, seq))
}
func (c *channel) recvWindowUpdate(seq uint64) {
c.m.Lock()
c.write.allowed = seq
c.c.Signal()
c.m.Unlock()
}
func (c *channel) Read(p []byte) (int, error) {
n, err := c.readPipe.Read(p)
c.m.Lock()
c.read.current = c.read.current + uint64(n)
needUpdate := c.read.isAlmostClosed()
c.m.Unlock()
if needUpdate {
c.sendWindowUpdate()
}
return n, err
}
// for unit testing only
func (c *channel) setTestAllowDataAfterCloseWrite() {
c.testAllowDataAfterCloseWrite = true
}
func (c *channel) Write(p []byte) (int, error) {
c.m.Lock()
defer c.m.Unlock()
written := 0
for {
if len(p) == 0 {
return written, nil
}
if c.closeReceived || c.closeSent || (c.shutdownSent && !c.testAllowDataAfterCloseWrite) {
return written, io.EOF
}
if c.write.size() > 0 {
toWrite := c.write.size()
if toWrite > len(p) {
toWrite = len(p)
}
// Don't block holding the metadata mutex.
// Note this would allow concurrent calls to Write on the same channel
// to conflict, but we regard that as user error.
c.m.Unlock()
// need to write the header and the payload together
c.multiplexer.writeMutex.Lock()
f := NewData(c.ID, uint32(toWrite))
c.multiplexer.appendEvent(&event{eventType: eventSend, frame: f})
err1 := f.Write(c.multiplexer.connW)
_, err2 := c.multiplexer.connW.Write(p[0:toWrite])
err3 := c.multiplexer.connW.Flush()
c.multiplexer.writeMutex.Unlock()
c.m.Lock()
if err1 != nil {
return written, err1
}
if err2 != nil {
return written, err2
}
if err3 != nil {
return written, err3
}
c.write.current = c.write.current + uint64(toWrite)
p = p[toWrite:]
written = written + toWrite
continue
}
// Wait for the write window to be increased (or a timeout)
done := make(chan struct{})
timeout := make(chan time.Time)
if !c.writeDeadline.IsZero() {
go func() {
time.Sleep(time.Until(c.writeDeadline))
close(timeout)
}()
}
go func() {
c.c.Wait()
close(done)
}()
select {
case <-timeout:
// clean up the goroutine
c.c.Broadcast()
<-done
return written, &errTimeout{}
case <-done:
// The timeout will still fire in the background
continue
}
}
}
func (c *channel) Close() error {
// Avoid a Write() racing with us and sending after we Close()
// Avoid sending Close twice
c.m.Lock()
alreadyClosed := c.closeSent
c.closeSent = true
c.m.Unlock()
if alreadyClosed {
return nil
}
if err := c.multiplexer.send(NewClose(c.ID)); err != nil {
return err
}
c.m.Lock()
defer c.m.Unlock()
c.c.Broadcast()
c.multiplexer.decrChannelRef(c.ID)
return nil
}
func (c *channel) CloseRead() error {
return c.readPipe.CloseWrite()
}
func (c *channel) CloseWrite() error {
// Avoid a Write() racing with us and sending after we Close()
// Avoid sending Shutdown twice
c.m.Lock()
alreadyShutdown := c.shutdownSent || c.closeSent
c.shutdownSent = true
c.m.Unlock()
if alreadyShutdown {
return nil
}
if err := c.multiplexer.send(NewShutdown(c.ID)); err != nil {
return err
}
c.m.Lock()
defer c.m.Unlock()
c.c.Broadcast()
return nil
}
func (c *channel) recvClose() {
c.m.Lock()
defer c.m.Unlock()
c.closeReceived = true
c.c.Broadcast()
}
func (c *channel) SetReadDeadline(timeout time.Time) error {
return c.readPipe.SetReadDeadline(timeout)
}
func (c *channel) SetWriteDeadline(timeout time.Time) error {
c.m.Lock()
defer c.m.Unlock()
c.writeDeadline = timeout
c.c.Broadcast()
return nil
}
func (c *channel) SetDeadline(timeout time.Time) error {
if err := c.SetReadDeadline(timeout); err != nil {
return err
}
return c.SetWriteDeadline(timeout)
}
func (c *channel) RemoteAddr() net.Addr {
return c
}
func (c *channel) LocalAddr() net.Addr {
return c.RemoteAddr() // There is no local address
}
func (c *channel) Network() string {
return "channel"
}
const (
eventSend = 0
eventRecv = 1
eventOpen = 2
eventClose = 3
)
type event struct {
eventType int
frame *Frame // for eventSend and eventRecv
id uint32 // for eventOpen and eventClose
destination Destination // for eventOpen and eventClose
}
func (e *event) String() string {
switch e.eventType {
case eventSend:
return fmt.Sprintf("send %s", e.frame.String())
case eventRecv:
return fmt.Sprintf("recv %s", e.frame.String())
case eventOpen:
return fmt.Sprintf("open %d -> %s", e.id, e.destination)
case eventClose:
return fmt.Sprintf("close %d -> %s", e.id, e.destination)
default:
return "unknown trace event"
}
}
// Multiplexer muxes and demuxes sub-connections over a single connection
type Multiplexer interface {
Run() // Run the multiplexer (otherwise Dial, Accept will not work)
IsRunning() bool // IsRunning is true if the multiplexer is running normally, false if it has failed
Dial(d Destination) (Conn, error) // Dial a remote Destination
Accept() (Conn, *Destination, error) // Accept a connection from a remote Destination
Close() error // Close the multiplexer
DumpState(w io.Writer) // WriteState dumps debug state to the writer
}
type multiplexer struct {
label string
conn io.Closer
connR io.Reader // with buffering
connW *bufio.Writer
writeMutex *sync.Mutex // hold when writing on the channel
channels map[uint32]*channel
nextChannelID uint32
metadataMutex *sync.Mutex // hold when reading/modifying this structure
pendingAccept []*channel // incoming connections
acceptCond *sync.Cond
isRunning bool
events *ring.Ring // log of packetEvents
eventsM *sync.Mutex
allocateBackwards bool
}
// NewMultiplexer constructs a multiplexer from a channel
func NewMultiplexer(label string, conn io.ReadWriteCloser, allocateBackwards bool) (Multiplexer, error) {
var writeMutex, metadataMutex, eventsM sync.Mutex
acceptCond := sync.NewCond(&metadataMutex)
channels := make(map[uint32]*channel)
connR := bufio.NewReader(conn)
connW := bufio.NewWriter(conn)
events := ring.New(500)
// Perform the handshake
localH := &handshake{}
g := &errgroup.Group{}
g.Go(func() error { return localH.Write(conn) })
g.Go(func() error {
_, err := unmarshalHandshake(connR)
return err
})
if err := g.Wait(); err != nil {
return nil, err
}
nextId := uint32(0)
if allocateBackwards {
nextId = ^nextId
}
return &multiplexer{
label: label,
conn: conn,
connR: connR,
connW: connW,
writeMutex: &writeMutex,
channels: channels,
metadataMutex: &metadataMutex,
acceptCond: acceptCond,
nextChannelID: nextId,
events: events,
eventsM: &eventsM,
allocateBackwards: allocateBackwards,
}, nil
}
// Close the underlying transport.
func (m *multiplexer) Close() error {
return m.conn.Close()
}
func (m *multiplexer) appendEvent(e *event) {
m.eventsM.Lock()
defer m.eventsM.Unlock()
m.events.Value = e
m.events = m.events.Next()
}
func (m *multiplexer) send(f *Frame) error {
m.writeMutex.Lock()
defer m.writeMutex.Unlock()
if err := f.Write(m.connW); err != nil {
return err
}
m.appendEvent(&event{eventType: eventSend, frame: f})
return m.connW.Flush()
}
func (m *multiplexer) findFreeChannelID() uint32 {
// the metadataMutex is already held
if m.allocateBackwards {
id := m.nextChannelID
for {
if _, ok := m.channels[id]; !ok {
m.nextChannelID = id - 1
return id
}
id--
}
}
id := m.nextChannelID
for {
if _, ok := m.channels[id]; !ok {
m.nextChannelID = id + 1
return id
}
id++
}
}
func (m *multiplexer) decrChannelRef(ID uint32) {
m.metadataMutex.Lock()
defer m.metadataMutex.Unlock()
if channel, ok := m.channels[ID]; ok {
if channel.refCount == 1 {
m.appendEvent(&event{eventType: eventClose, id: ID, destination: channel.destination})
delete(m.channels, ID)
return
}
channel.refCount = channel.refCount - 1
}
}
// Dial opens a connection to the given destination
func (m *multiplexer) Dial(d Destination) (Conn, error) {
m.metadataMutex.Lock()
if !m.isRunning {
m.metadataMutex.Unlock()
return nil, errors.New("connection refused")
}
id := m.findFreeChannelID()
channel := newChannel(m, id, d)
m.channels[id] = channel
m.metadataMutex.Unlock()
if err := m.send(NewOpen(id, d)); err != nil {
return nil, err
}
if err := channel.sendWindowUpdate(); err != nil {
return nil, err
}
if d.Proto == UDP {
// remove encapsulation
return newUDPConn(channel), nil
}
return channel, nil
}
// Accept returns the next client connection
func (m *multiplexer) Accept() (Conn, *Destination, error) {
m.metadataMutex.Lock()
defer m.metadataMutex.Unlock()
for {
if !m.isRunning {
return nil, nil, errors.New("accept: multiplexer is not running")
}
if len(m.pendingAccept) > 0 {
first := m.pendingAccept[0]
m.pendingAccept = m.pendingAccept[1:]
if err := first.sendWindowUpdate(); err != nil {
return nil, nil, err
}
if first.destination.Proto == UDP {
return newUDPConn(first), &first.destination, nil
}
return first, &first.destination, nil
}
m.acceptCond.Wait()
}
}
// Run starts handling the requests from the other side
func (m *multiplexer) Run() {
m.metadataMutex.Lock()
m.isRunning = true
m.metadataMutex.Unlock()
go func() {
if err := m.run(); err != nil {
if err == io.EOF {
// This is expected when the data connection is broken
log.Infof("disconnected data connection: multiplexer is offline")
} else {
log.Printf("Multiplexer main loop failed with %v", err)
m.DumpState(log.Writer())
}
}
m.metadataMutex.Lock()
m.isRunning = false
m.acceptCond.Broadcast()
var channels []*channel
for _, channel := range m.channels {
channels = append(channels, channel)
}
m.metadataMutex.Unlock()
// close all open channels
for _, channel := range channels {
// this will unblock waiting Read calls
channel.readPipe.closeWriteNoErr()
// this will unblock waiting Write calls
channel.recvClose()
m.decrChannelRef(channel.ID)
}
}()
}
// DumpState writes internal multiplexer state
func (m *multiplexer) DumpState(w io.Writer) {
m.eventsM.Lock()
io.WriteString(w, "Event trace:\n")
m.events.Do(func(p interface{}) {
if e, ok := p.(*event); ok {
io.WriteString(w, e.String())
io.WriteString(w, "\n")
}
})
m.eventsM.Unlock()
m.metadataMutex.Lock()
io.WriteString(w, "Active channels:\n")
for _, c := range m.channels {
io.WriteString(w, c.String())
io.WriteString(w, "\n")
}
io.WriteString(w, "End of state dump\n")
m.metadataMutex.Unlock()
}
// IsRunning returns whether the multiplexer is running or not
func (m *multiplexer) IsRunning() bool {
m.metadataMutex.Lock()
defer m.metadataMutex.Unlock()
return m.isRunning
}
func (m *multiplexer) run() error {
for {
f, err := unmarshalFrame(m.connR)
if err != nil {
return err
}
m.appendEvent(&event{eventType: eventRecv, frame: f})
switch payload := f.Payload().(type) {
case *OpenFrame:
o, err := f.Open()
if err != nil {
return fmt.Errorf("Failed to unmarshal open command: %v", err)
}
switch o.Connection {
case Dedicated:
return fmt.Errorf("Dedicated connections are not implemented yet")
case Multiplexed:
m.metadataMutex.Lock()
channel := newChannel(m, f.ID, o.Destination)
m.channels[f.ID] = channel
m.pendingAccept = append(m.pendingAccept, channel)
m.acceptCond.Signal()
m.metadataMutex.Unlock()
m.appendEvent(&event{eventType: eventOpen, id: f.ID, destination: o.Destination})
}
case *WindowFrame:
m.metadataMutex.Lock()
channel, ok := m.channels[f.ID]
m.metadataMutex.Unlock()
if !ok {
return fmt.Errorf("Unknown channel id %s", f.String())
}
channel.recvWindowUpdate(payload.seq)
case *DataFrame:
m.metadataMutex.Lock()
channel, ok := m.channels[f.ID]
m.metadataMutex.Unlock()
if !ok {
return fmt.Errorf("Unknown channel id: %s", f.String())
}
// We don't use a direct io.Copy or io.CopyN to the readPipe because if they get
// EOF on Write, they will drop the data in the buffer and we don't know how big
// it was so we can't avoid desychronising the stream.
// We trust the clients not to write more than a Window size.
var buf bytes.Buffer
if _, err := io.CopyN(&buf, m.connR, int64(payload.payloadlen)); err != nil {
return fmt.Errorf("Failed to read payload of %d bytes: %s", payload.payloadlen, f.String())
}
if n, err := io.Copy(channel.readPipe, &buf); err != nil {
// err must be io.EOF
log.Printf("Discarded %d bytes from %s", int64(payload.payloadlen)-n, f.String())
// A confused client could send a DataFrame after a ShutdownFrame or CloseFrame.
// The stream is not desychronised so we can keep going.
}
case *ShutdownFrame:
m.metadataMutex.Lock()
channel, ok := m.channels[f.ID]
m.metadataMutex.Unlock()
if !ok {
return fmt.Errorf("Unknown channel id: %s", f.String())
}
channel.readPipe.closeWriteNoErr()
case *CloseFrame:
m.metadataMutex.Lock()
channel, ok := m.channels[f.ID]
m.metadataMutex.Unlock()
if !ok {
return fmt.Errorf("Unknown channel id: %s", f.String())
}
// this will unblock waiting Read calls
channel.readPipe.closeWriteNoErr()
// this will unblock waiting Write calls
channel.recvClose()
m.decrChannelRef(channel.ID)
default:
return fmt.Errorf("Unknown command type: %v", f)
}
}
}

View File

@ -0,0 +1,74 @@
// Package libproxy provides a network Proxy interface and implementations for TCP
// and UDP.
package libproxy
import (
"fmt"
"net"
"os"
"syscall"
)
// Proxy defines the behavior of a proxy. It forwards traffic back and forth
// between two endpoints : the frontend and the backend.
// It can be used to do software port-mapping between two addresses.
// e.g. forward all traffic between the frontend (host) 127.0.0.1:3000
// to the backend (container) at 172.17.42.108:4000.
type Proxy interface {
// Run starts forwarding traffic back and forth between the front
// and back-end addresses.
Run()
// Close stops forwarding traffic and close both ends of the Proxy.
Close()
// FrontendAddr returns the address on which the proxy is listening.
FrontendAddr() net.Addr
// BackendAddr returns the proxied address.
BackendAddr() net.Addr
}
// NewIPProxy creates a Proxy according to the specified frontendAddr and backendAddr.
func NewIPProxy(frontendAddr, backendAddr net.Addr) (Proxy, error) {
switch frontendAddr.(type) {
case *net.UDPAddr:
listener, err := net.ListenUDP("udp", frontendAddr.(*net.UDPAddr))
if err != nil {
return nil, err
}
return NewUDPProxy(listener.LocalAddr().(*net.UDPAddr), listener, backendAddr.(*net.UDPAddr), nil)
case *net.TCPAddr:
listener, err := net.Listen("tcp", frontendAddr.String())
if err != nil {
return nil, err
}
return NewTCPProxy(listener, backendAddr.(*net.TCPAddr))
case *net.UnixAddr:
listener, err := net.Listen("unix", frontendAddr.String())
if err != nil {
return nil, err
}
return NewUnixProxy(listener, backendAddr.(*net.UnixAddr))
default:
panic(fmt.Errorf("Unsupported protocol"))
}
}
// NewBestEffortIPProxy Best-effort attempt to listen on the address in the VM. This is for
// backwards compatibility with software that expects to be able to listen on
// 0.0.0.0 and then connect from within a container to the external port.
// If the address doesn't exist in the VM (i.e. it exists only on the host)
// then this is not a hard failure.
func NewBestEffortIPProxy(host net.Addr, container net.Addr) (Proxy, error) {
ipP, err := NewIPProxy(host, container)
if err == nil {
return ipP, nil
}
if opError, ok := err.(*net.OpError); ok {
if syscallError, ok := opError.Err.(*os.SyscallError); ok {
if syscallError.Err == syscall.EADDRNOTAVAIL {
log.Printf("Address %s doesn't exist in the VM: only binding on the host", host)
return nil, nil // Non-fatal error
}
}
}
return nil, err
}

View File

@ -0,0 +1,61 @@
package libproxy
import (
"io"
"net"
"strings"
)
// Conn defines a network connection
type Conn interface {
net.Conn
CloseWrite() error
}
// ProxyStream data between client and backend, until both are at EOF or quit is closed.
func ProxyStream(client, backend Conn, quit <-chan struct{}) error {
event := make(chan int64)
var broker = func(to, from Conn) {
written, err := io.Copy(to, from)
if err != nil && err != io.EOF && !errIsBeingClosed(err) {
log.Println("error copying:", err)
}
err = to.CloseWrite()
if err != nil && !errIsNotConnected(err) && !errIsBeingClosed(err) {
log.Println("error CloseWrite to:", err)
}
event <- written
}
go broker(client, backend)
go broker(backend, client)
var transferred int64
for i := 0; i < 2; i++ {
select {
case written := <-event:
transferred += written
case <-quit:
// Interrupt the two brokers and "join" them.
backend.Close()
for ; i < 2; i++ {
transferred += <-event
}
return nil
}
}
backend.Close()
return nil
}
func errIsNotConnected(err error) bool {
return strings.HasSuffix(err.Error(), "is not connected")
}
func errIsConnectionRefused(err error) bool {
return strings.HasSuffix(err.Error(), "connection refused")
}
func errIsBeingClosed(err error) bool {
return strings.HasSuffix(err.Error(), "is being closed.")
}

View File

@ -0,0 +1,31 @@
package libproxy
import (
"net"
)
// StubProxy is a proxy that is a stub (does nothing).
type StubProxy struct {
frontendAddr net.Addr
backendAddr net.Addr
}
// Run does nothing.
func (p *StubProxy) Run() {}
// Close does nothing.
func (p *StubProxy) Close() {}
// FrontendAddr returns the frontend address.
func (p *StubProxy) FrontendAddr() net.Addr { return p.frontendAddr }
// BackendAddr returns the backend address.
func (p *StubProxy) BackendAddr() net.Addr { return p.backendAddr }
// NewStubProxy creates a new StubProxy
func NewStubProxy(frontendAddr, backendAddr net.Addr) (Proxy, error) {
return &StubProxy{
frontendAddr: frontendAddr,
backendAddr: backendAddr,
}, nil
}

View File

@ -0,0 +1,60 @@
package libproxy
import (
"fmt"
"net"
)
// TCPProxy is a proxy for TCP connections. It implements the Proxy interface to
// handle TCP traffic forwarding between the frontend and backend addresses.
type TCPProxy struct {
listener net.Listener
frontendAddr net.Addr
backendAddr *net.TCPAddr
}
// NewTCPProxy creates a new TCPProxy.
func NewTCPProxy(listener net.Listener, backendAddr *net.TCPAddr) (*TCPProxy, error) {
// If the port in frontendAddr was 0 then ListenTCP will have a picked
// a port to listen on, hence the call to Addr to get that actual port:
return &TCPProxy{
listener: listener,
frontendAddr: listener.Addr(),
backendAddr: backendAddr,
}, nil
}
// HandleTCPConnection forwards the TCP traffic to a specified backend address
func HandleTCPConnection(client Conn, backendAddr *net.TCPAddr, quit <-chan struct{}) error {
backend, err := net.DialTCP("tcp", nil, backendAddr)
if err != nil {
if errIsConnectionRefused(err) {
return err
}
return fmt.Errorf("can't forward traffic to backend tcp/%v: %s", backendAddr, err)
}
return ProxyStream(client, backend, quit)
}
// Run starts forwarding the traffic using TCP.
func (proxy *TCPProxy) Run() {
quit := make(chan struct{})
defer close(quit)
for {
client, err := proxy.listener.Accept()
if err != nil {
log.Printf("Stopping proxy on tcp/%v for tcp/%v (%s)", proxy.frontendAddr, proxy.backendAddr, err)
return
}
go HandleTCPConnection(client.(Conn), proxy.backendAddr, quit)
}
}
// Close stops forwarding the traffic.
func (proxy *TCPProxy) Close() { proxy.listener.Close() }
// FrontendAddr returns the TCP address on which the proxy is listening.
func (proxy *TCPProxy) FrontendAddr() net.Addr { return proxy.frontendAddr }
// BackendAddr returns the TCP proxied address.
func (proxy *TCPProxy) BackendAddr() net.Addr { return proxy.backendAddr }

View File

@ -0,0 +1,202 @@
package libproxy
import (
"bytes"
"encoding/binary"
"io"
"net"
"sync"
"time"
)
// UDPListener defines a listener interface to read, write and close a UDP connection
type UDPListener interface {
ReadFromUDP(b []byte) (int, *net.UDPAddr, error)
WriteToUDP(b []byte, addr *net.UDPAddr) (int, error)
Close() error
}
// UDPEncapsulator implements net.Conn and reads and writes UDP datagrams framed within a stream connection
type uDPEncapsulator interface {
Conn
ReadFromUDP(b []byte) (int, *net.UDPAddr, error)
WriteToUDP(b []byte, addr *net.UDPAddr) (int, error)
}
// udpEncapsulator encapsulates a UDP connection and listener
type udpEncapsulator struct {
conn net.Conn
m *sync.Mutex
r *sync.Mutex
w *sync.Mutex
addr *net.UDPAddr
}
// ReadFromUDP reads the bytestream from a udpEncapsulator, returning the
// number of bytes read and the unpacked UDPAddr struct
func (u *udpEncapsulator) ReadFromUDP(b []byte) (int, *net.UDPAddr, error) {
u.r.Lock()
defer u.r.Unlock()
datagram := &udpDatagram{payload: b}
length, err := datagram.Unmarshal(u.conn)
if err != nil {
return 0, nil, err
}
udpAddr := net.UDPAddr{IP: *datagram.IP, Port: int(datagram.Port), Zone: datagram.Zone}
return length, &udpAddr, nil
}
// WriteToUDP writes a bytestream to a specified UDPAddr, returning the number
// of bytes successfully written
func (u *udpEncapsulator) WriteToUDP(b []byte, addr *net.UDPAddr) (int, error) {
u.w.Lock()
defer u.w.Unlock()
datagram := &udpDatagram{payload: b, IP: &addr.IP, Port: uint16(addr.Port), Zone: addr.Zone}
return len(b), datagram.Marshal(u.conn)
}
// Close closes the connection in the udpEncapsulator
func (u *udpEncapsulator) Close() error {
return u.conn.Close()
}
func (u *udpEncapsulator) CloseWrite() error {
return nil
}
func (u *udpEncapsulator) Read(b []byte) (int, error) {
n, _, err := u.ReadFromUDP(b)
return n, err
}
func (u *udpEncapsulator) Write(b []byte) (int, error) {
return u.WriteToUDP(b, &net.UDPAddr{})
}
func (u *udpEncapsulator) LocalAddr() net.Addr {
return u.conn.LocalAddr()
}
func (u *udpEncapsulator) RemoteAddr() net.Addr {
return u.conn.RemoteAddr()
}
func (u *udpEncapsulator) SetDeadline(t time.Time) error {
return u.conn.SetDeadline(t)
}
func (u *udpEncapsulator) SetReadDeadline(t time.Time) error {
return u.conn.SetReadDeadline(t)
}
func (u *udpEncapsulator) SetWriteDeadline(t time.Time) error {
return u.conn.SetWriteDeadline(t)
}
func (u *udpEncapsulator) Connect(a *net.UDPAddr) {
u.addr = a
}
// newUDPConn initializes a new UDP connection
func newUDPConn(conn net.Conn) uDPEncapsulator {
var m sync.Mutex
var r sync.Mutex
var w sync.Mutex
return &udpEncapsulator{
conn: conn,
m: &m,
r: &r,
w: &w,
}
}
type udpDatagram struct {
payload []byte
IP *net.IP
Port uint16
Zone string
}
// Marshal marshals data from the udpDatagram to the provided connection
func (u *udpDatagram) Marshal(w io.Writer) error {
// marshal the variable length header to a temporary buffer
var header bytes.Buffer
length := uint16(len(*u.IP))
if err := binary.Write(&header, binary.LittleEndian, &length); err != nil {
return err
}
if err := binary.Write(&header, binary.LittleEndian, u.IP); err != nil {
return err
}
if err := binary.Write(&header, binary.LittleEndian, &u.Port); err != nil {
return err
}
length = uint16(len(u.Zone))
if err := binary.Write(&header, binary.LittleEndian, &length); err != nil {
return err
}
if err := binary.Write(&header, binary.LittleEndian, []byte(u.Zone)); err != nil {
return nil
}
length = uint16(len(u.payload))
if err := binary.Write(&header, binary.LittleEndian, &length); err != nil {
return nil
}
length = uint16(2 + header.Len() + len(u.payload))
if err := binary.Write(w, binary.LittleEndian, &length); err != nil {
return nil
}
_, err := io.Copy(w, &header)
if err != nil {
return err
}
payload := bytes.NewBuffer(u.payload)
_, err = io.Copy(w, payload)
if err != nil {
return err
}
return nil
}
// Unmarshal unmarshals data from the connection to the udpDatagram
func (u *udpDatagram) Unmarshal(r io.Reader) (int, error) {
var length uint16
// frame length
if err := binary.Read(r, binary.LittleEndian, &length); err != nil {
return 0, err
}
if err := binary.Read(r, binary.LittleEndian, &length); err != nil {
return 0, err
}
var IP net.IP
IP = make([]byte, length)
if err := binary.Read(r, binary.LittleEndian, &IP); err != nil {
return 0, err
}
u.IP = &IP
if err := binary.Read(r, binary.LittleEndian, &u.Port); err != nil {
return 0, err
}
if err := binary.Read(r, binary.LittleEndian, &length); err != nil {
return 0, err
}
Zone := make([]byte, length)
if err := binary.Read(r, binary.LittleEndian, &Zone); err != nil {
return 0, err
}
u.Zone = string(Zone)
if err := binary.Read(r, binary.LittleEndian, &length); err != nil {
return 0, err
}
_, err := io.ReadFull(r, u.payload[0:length])
if err != nil {
return 0, err
}
return int(length), nil
}

View File

@ -0,0 +1,178 @@
package libproxy
import (
"encoding/binary"
"net"
"strings"
"sync"
"syscall"
"time"
)
const (
// UDPConnTrackTimeout is the timeout used for UDP connection tracking
UDPConnTrackTimeout = 90 * time.Second
// UDPBufSize is the buffer size for the UDP proxy
UDPBufSize = 65507
)
// A net.Addr where the IP is split into two fields so you can use it as a key
// in a map:
type connTrackKey struct {
IPHigh uint64
IPLow uint64
Port int
}
func newConnTrackKey(addr *net.UDPAddr) *connTrackKey {
if len(addr.IP) == net.IPv4len {
return &connTrackKey{
IPHigh: 0,
IPLow: uint64(binary.BigEndian.Uint32(addr.IP)),
Port: addr.Port,
}
}
return &connTrackKey{
IPHigh: binary.BigEndian.Uint64(addr.IP[:8]),
IPLow: binary.BigEndian.Uint64(addr.IP[8:]),
Port: addr.Port,
}
}
type connTrackMap map[connTrackKey]net.Conn
// UDPProxy is proxy for which handles UDP datagrams. It implements the Proxy
// interface to handle UDP traffic forwarding between the frontend and backend
// addresses.
type UDPProxy struct {
listener UDPListener
frontendAddr net.Addr
backendAddr *net.UDPAddr
dialer UDPDialer
connTrackTable connTrackMap
connTrackLock sync.Mutex
}
// UDPDialer creates UDP (pseudo-)connections to an address
type UDPDialer interface {
Dial(*net.UDPAddr) (net.Conn, error)
}
type defaultUDPDialer struct{}
func (d *defaultUDPDialer) Dial(addr *net.UDPAddr) (net.Conn, error) {
return net.DialUDP("udp", nil, addr)
}
// NewUDPProxy creates a new UDPProxy.
func NewUDPProxy(frontendAddr net.Addr, listener UDPListener, backendAddr *net.UDPAddr, dialer UDPDialer) (*UDPProxy, error) {
if dialer == nil {
dialer = &defaultUDPDialer{}
}
return &UDPProxy{
listener: listener,
frontendAddr: frontendAddr,
backendAddr: backendAddr,
dialer: dialer,
connTrackTable: make(connTrackMap),
}, nil
}
func (proxy *UDPProxy) replyLoop(proxyConn net.Conn, clientAddr *net.UDPAddr, clientKey *connTrackKey) {
defer func() {
proxy.connTrackLock.Lock()
delete(proxy.connTrackTable, *clientKey)
proxy.connTrackLock.Unlock()
proxyConn.Close()
}()
readBuf := make([]byte, UDPBufSize)
for {
proxyConn.SetReadDeadline(time.Now().Add(UDPConnTrackTimeout))
again:
read, err := proxyConn.Read(readBuf)
if err != nil {
if err, ok := err.(*net.OpError); ok && err.Err == syscall.ECONNREFUSED {
// This will happen if the last write failed
// (e.g: nothing is actually listening on the
// proxied port on the container), ignore it
// and continue until UDPConnTrackTimeout
// expires:
goto again
}
return
}
for i := 0; i != read; {
written, err := proxy.listener.WriteToUDP(readBuf[i:read], clientAddr)
if err != nil {
return
}
i += written
}
}
}
// Run starts forwarding the traffic using UDP.
func (proxy *UDPProxy) Run() {
readBuf := make([]byte, UDPBufSize)
for {
read, from, err := proxy.listener.ReadFromUDP(readBuf)
if err != nil {
// NOTE: Apparently ReadFrom doesn't return
// ECONNREFUSED like Read do (see comment in
// UDPProxy.replyLoop)
if !isClosedError(err) {
log.Printf("Stopping proxy on %v for udp/%v (%s)", proxy.frontendAddr, proxy.backendAddr, err)
}
break
}
fromKey := newConnTrackKey(from)
proxy.connTrackLock.Lock()
proxyConn, hit := proxy.connTrackTable[*fromKey]
if !hit {
proxyConn, err = proxy.dialer.Dial(proxy.backendAddr)
if err != nil {
log.Printf("Can't proxy a datagram to udp/%s: %s\n", proxy.backendAddr, err)
proxy.connTrackLock.Unlock()
continue
}
proxy.connTrackTable[*fromKey] = proxyConn
go proxy.replyLoop(proxyConn, from, fromKey)
}
proxy.connTrackLock.Unlock()
for i := 0; i != read; {
written, err := proxyConn.Write(readBuf[i:read])
if err != nil {
log.Printf("Can't proxy a datagram to udp/%s: %s\n", proxy.backendAddr, err)
break
}
i += written
}
}
}
// Close stops forwarding the traffic.
func (proxy *UDPProxy) Close() {
proxy.listener.Close()
proxy.connTrackLock.Lock()
defer proxy.connTrackLock.Unlock()
for _, conn := range proxy.connTrackTable {
conn.Close()
}
}
// FrontendAddr returns the UDP address on which the proxy is listening.
func (proxy *UDPProxy) FrontendAddr() net.Addr { return proxy.frontendAddr }
// BackendAddr returns the proxied UDP address.
func (proxy *UDPProxy) BackendAddr() net.Addr { return proxy.backendAddr }
func isClosedError(err error) bool {
/* This comparison is ugly, but unfortunately, net.go doesn't export errClosing.
* See:
* http://golang.org/src/pkg/net/net.go
* https://code.google.com/p/go/issues/detail?id=4337
* https://groups.google.com/forum/#!msg/golang-nuts/0_aaCvBmOcM/SptmDyX1XJMJ
*/
return strings.HasSuffix(err.Error(), "use of closed network connection")
}

View File

@ -0,0 +1,69 @@
package libproxy
import (
"fmt"
"net"
"time"
)
// UnixProxy is a proxy for Unix connections. It implements the Proxy interface to
// handle Unix traffic forwarding between the frontend and backend addresses.
type UnixProxy struct {
listener net.Listener
frontendAddr net.Addr
backendAddr *net.UnixAddr
}
// NewUnixProxy creates a new UnixProxy.
func NewUnixProxy(listener net.Listener, backendAddr *net.UnixAddr) (*UnixProxy, error) {
log.Printf("NewUnixProxy from %s -> %s\n", listener.Addr().String(), backendAddr.String())
return &UnixProxy{
listener: listener,
frontendAddr: listener.Addr(),
backendAddr: backendAddr,
}, nil
}
// HandleUnixConnection forwards the Unix traffic to a specified backend address
func HandleUnixConnection(client Conn, backendAddr *net.UnixAddr, quit <-chan struct{}) error {
start := time.Now()
for {
backend, err := net.DialUnix("unix", nil, backendAddr)
if err != nil {
if errIsConnectionRefused(err) {
if time.Since(start) > 120*time.Second {
log.Errorf("failed to connect to %s after 120s. The server appears to be down.", backendAddr.String())
return err
}
log.Infof("%s appears to not be started yet: will retry in 5s", backendAddr.String())
time.Sleep(5 * time.Second)
continue
}
return fmt.Errorf("can't forward traffic to backend unix/%v: %s", backendAddr, err)
}
return ProxyStream(client, backend, quit)
}
}
// Run starts forwarding the traffic using Unix.
func (proxy *UnixProxy) Run() {
quit := make(chan struct{})
defer close(quit)
for {
client, err := proxy.listener.Accept()
if err != nil {
log.Printf("Stopping proxy on unix/%v for unix/%v (%s)", proxy.frontendAddr, proxy.backendAddr, err)
return
}
go HandleUnixConnection(client.(Conn), proxy.backendAddr, quit)
}
}
// Close stops forwarding the traffic.
func (proxy *UnixProxy) Close() { proxy.listener.Close() }
// FrontendAddr returns the Unix address on which the proxy is listening.
func (proxy *UnixProxy) FrontendAddr() net.Addr { return proxy.frontendAddr }
// BackendAddr returns the Unix proxied address.
func (proxy *UnixProxy) BackendAddr() net.Addr { return proxy.backendAddr }

View File

@ -1,4 +1,4 @@
package vpnkit
package vmnet
import (
"bytes"
@ -8,7 +8,6 @@ import (
"fmt"
"io"
"net"
"os"
"time"
"github.com/google/uuid"
@ -21,8 +20,8 @@ type Vmnet struct {
remoteVersion *InitMessage
}
// NewVmnet constructs an instance of Vmnet.
func NewVmnet(ctx context.Context, path string) (*Vmnet, error) {
// New constructs an instance of Vmnet.
func New(ctx context.Context, path string) (*Vmnet, error) {
d := &net.Dialer{}
conn, err := d.DialContext(ctx, "unix", path)
if err != nil {
@ -562,24 +561,12 @@ func (v *Vif) dhcp() (net.IP, error) {
ethernet := NewEthernetFrame(broadcastMAC, v.ClientMAC, 0x800)
ethernet.setData(ipv4.Bytes())
file, err := os.Create("/tmp/go.pcap")
if err != nil {
panic(err)
}
pcap, err := NewPcapWriter(file)
if err != nil {
panic(err)
}
finished := false
go func() {
for !finished {
if err := v.Write(ethernet.Bytes()); err != nil {
panic(err)
}
if err := pcap.Write(ethernet.Bytes()); err != nil {
panic(err)
}
time.Sleep(time.Second)
}
}()
@ -589,9 +576,6 @@ func (v *Vif) dhcp() (net.IP, error) {
if err != nil {
return nil, err
}
if err := pcap.Write(response); err != nil {
panic(err)
}
ethernet, err = ParseEthernetFrame(response)
if err != nil {
continue

View File

@ -0,0 +1,134 @@
package vpnkit
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"time"
"github.com/moby/vpnkit/go/pkg/vpnkit/transport"
)
const (
ListPath = "/forwards/list"
ExposePortPath = "/forwards/expose/port"
ExposePipePath = "/forwards/expose/pipe"
UnexposePortPath = "/forwards/unexpose/port"
UnexposePipePath = "/forwards/unexpose/pipe"
DumpStatePath = "/forwards/dump"
)
const httpTimeout = 120 * time.Second
func NewClient(path string) (Client, error) {
t := transport.Choose(path)
return &httpClient{
client: http.Client{
Timeout: httpTimeout,
Transport: &http.Transport{
DialContext: func(c context.Context, _, _ string) (net.Conn, error) {
return t.Dial(c, path)
},
},
},
}, nil
}
type httpClient struct {
client http.Client
}
// ExposeError should be reported through to the user
type ExposeError struct {
Message string `json:"message"`
}
func (e *ExposeError) Error() string {
return e.Message
}
func (h *httpClient) Expose(_ context.Context, port *Port) error {
var buf bytes.Buffer
enc := json.NewEncoder(&buf)
if err := enc.Encode(port); err != nil {
return err
}
path := ExposePortPath
if port.Proto == Unix {
path = ExposePipePath
}
res, err := h.client.Post("http://unix"+path, "application/json", &buf)
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode == 400 {
var exposeError ExposeError
dec := json.NewDecoder(res.Body)
if err := dec.Decode(&exposeError); err != nil {
fmt.Printf("failed to decode: %v\n", err)
return err
}
return &exposeError
}
if res.StatusCode != http.StatusOK {
return fmt.Errorf(path+" returned unexpected status: %d", res.StatusCode)
}
return nil
}
func (h *httpClient) Unexpose(_ context.Context, port *Port) error {
var buf bytes.Buffer
enc := json.NewEncoder(&buf)
if err := enc.Encode(port); err != nil {
return err
}
path := UnexposePortPath
if port.Proto == Unix {
path = UnexposePipePath
}
res, err := h.client.Post("http://unix"+path, "application/json", &buf)
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return fmt.Errorf(path+" returned unexpected status: %d", res.StatusCode)
}
return nil
}
func (h *httpClient) ListExposed(context.Context) ([]Port, error) {
res, err := h.client.Get("http://unix" + ListPath)
if err != nil {
fmt.Printf("GET failed with %v\n", err)
return nil, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return nil, fmt.Errorf(ListPath+" returned unexpected status: %d", res.StatusCode)
}
dec := json.NewDecoder(res.Body)
var ports []Port
if err := dec.Decode(&ports); err != nil {
return nil, err
}
return ports, nil
}
func (h *httpClient) DumpState(_ context.Context, w io.Writer) error {
res, err := h.client.Get("http://unix" + DumpStatePath)
if err != nil {
fmt.Printf("GET failed with %v\n", err)
return err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return fmt.Errorf(DumpStatePath+" returned unexpected status: %d", res.StatusCode)
}
_, err = io.Copy(w, res.Body)
return err
}

View File

@ -0,0 +1,60 @@
package vpnkit
import (
"encoding/json"
"io"
"github.com/pkg/errors"
)
// DHCPConfiguration configures the built-in DHCP server.
type DHCPConfiguration struct {
SearchDomains []string `json:"searchDomains"`
DomainName string `json:"domainName"`
}
func (d DHCPConfiguration) Write(w io.Writer) error {
enc := json.NewEncoder(w)
enc.SetIndent("", " ")
if err := enc.Encode(d); err != nil {
return errors.Wrap(err, "while writing DHCPConfiguration")
}
return nil
}
// HTTPConfiguration configures the built-in HTTP proxy.
type HTTPConfiguration struct {
HTTP string `json:"http,omitempty"`
HTTPS string `json:"https,omitempty"`
Exclude string `json:"exclude,omitempty"`
TransparentHTTPPorts []int `json:"transparent_http_ports"`
TransparentHTTPSPorts []int `json:"transparent_https_ports"`
}
func (h HTTPConfiguration) Write(w io.Writer) error {
enc := json.NewEncoder(w)
enc.SetIndent("", " ")
if err := enc.Encode(h); err != nil {
return errors.Wrap(err, "while writing HTTPConfiguration")
}
return nil
}
// Forward is a single forward from the gateway IP ExternalPort to (InternalIP, InternalPort)
type Forward struct {
Protocol Protocol `json:"protocol"`
ExternalPort int `json:"external_port"`
InternalIP string `json:"internal_ip"`
InternalPort int `json:"internal_port"`
}
// GatewayForwards is a list of individual forwards.
type GatewayForwards []Forward
func (g GatewayForwards) Write(w io.Writer) error {
enc := json.NewEncoder(w)
if err := enc.Encode(g); err != nil {
return errors.Wrap(err, "while writing GatewayForewards")
}
return nil
}

Some files were not shown because too many files have changed in this diff Show More