veyron/profiles: first pass at implementing profiles.
Please see comments in veyron/profiles/doc.go
Change-Id: I514c0dfe7d03221314d45686aadd5054e39324dc
diff --git a/products/lib/gce/gce.go b/products/lib/gce/gce.go
deleted file mode 100644
index 2d1cd7b..0000000
--- a/products/lib/gce/gce.go
+++ /dev/null
@@ -1,68 +0,0 @@
-// Package gce provides a way to test whether the current process is running on
-// Google Compute Engine, and to extract settings from this environment.
-package gce
-
-import (
- "io/ioutil"
- "net"
- "net/http"
- "sync"
- "time"
-)
-
-// This URL returns the external IP address assigned to the local GCE instance.
-// If a HTTP GET request fails for any reason, this is not a GCE instance. If
-// the result of the GET request doesn't contain a "Metadata-Flavor: Google"
-// header, it is also not a GCE instance. The body of the document contains the
-// external IP address, if present. Otherwise, the body is empty.
-const url = "http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/access-configs/0/external-ip"
-
-// How long to wait for the HTTP request to return.
-const timeout = time.Second
-
-var (
- once sync.Once
- isGCE bool
- externalIP net.IP
-)
-
-// RunningOnGoogleComputeEngine returns true if the current process is running
-// on a Google Compute Engine instance.
-func RunningOnGoogleComputeEngine() bool {
- once.Do(func() {
- isGCE, externalIP = googleComputeEngineTest(url)
- })
- return isGCE
-}
-
-// GoogleComputeEngineExternalIPAddress returns the external IP address of this
-// Google Compute Engine instance, or nil if there is none. Must be called after
-// RunningOnGoogleComputeEngine.
-func GoogleComputeEngineExternalIPAddress() net.IP {
- return externalIP
-}
-
-func googleComputeEngineTest(url string) (bool, net.IP) {
- client := &http.Client{Timeout: timeout}
- req, err := http.NewRequest("GET", url, nil)
- if err != nil {
- return false, nil
- }
- req.Header.Add("Metadata-Flavor", "Google")
- resp, err := client.Do(req)
- if err != nil {
- return false, nil
- }
- defer resp.Body.Close()
- if resp.StatusCode != 200 {
- return false, nil
- }
- if flavor := resp.Header["Metadata-Flavor"]; len(flavor) != 1 || flavor[0] != "Google" {
- return false, nil
- }
- body, err := ioutil.ReadAll(resp.Body)
- if err != nil {
- return true, nil
- }
- return true, net.ParseIP(string(body))
-}
diff --git a/profiles/doc.go b/profiles/doc.go
new file mode 100644
index 0000000..bfd4bfc
--- /dev/null
+++ b/profiles/doc.go
@@ -0,0 +1,67 @@
+// Package Profiles, and its children, provide implementations of the
+// veyron2.Profile interface. These implementations should import all of the
+// packages that they require to implement Profile-specific functionality.
+//
+// The taxonomy used to organise Profiles may be arbitrary and the directory
+// structure used here is just one convention for how to do so. This directory
+// structure reflects the generality of a Profile at any given depth in the
+// hierarchy, with higher levels of the directory structure being more
+// generic and lower levels more specific.
+//
+// Profiles register themselves by calling veyron2/rt.RegisterProfile in their
+// init function and are hence are chosen by importing them into an
+// applications main package. More specific packages may use functionality
+// exposed by more general packages and rely on go's module dependency
+// algorithm to execute the init function from the more specific package
+// after the less specific one and hence override the earlier Profile
+// registration.
+//
+// This top level directory contains a 'generic' Profile and utility routines
+// used by other Profiles. It does not follow the convention of registering
+// itself via its Init function, since the expected use is that it will
+// used automatically as a default by the Runtime. Instead it provides a New
+// function. This avoids the need for every main package to import
+// "veyron/profiles", instead, only more specific Profiles must be so imported.
+//
+// The 'net' Profile adds operating system support for varied network
+// configurations and in particular dhcp. It should be used by any application
+// that may 'roam' or any may be behind a 1-1 NAT.
+//
+// The 'net/bluetooth' Profile adds operating system support for bluetooth
+// networking.
+// TODO(cnicolaou,ashankar): add this
+package profiles
+
+import (
+ "veyron2"
+ "veyron2/config"
+)
+
+type generic struct{}
+
+// New returns a new instance of a very generic Profile. It can be used
+// as a default by Runtime implementations, in unit tests etc.
+func New() veyron2.Profile {
+ return &generic{}
+}
+
+func (g *generic) Name() string {
+ return "generic"
+}
+
+func (g *generic) Runtime() string {
+ return ""
+}
+
+func (g *generic) Platform() *veyron2.Platform {
+ p, _ := Platform()
+ return p
+}
+
+func (g *generic) Init(rt veyron2.Runtime, _ *config.Publisher) {
+ rt.Logger().VI(1).Infof("%s", g)
+}
+
+func (g *generic) String() string {
+ return "generic profile on " + g.Platform().String()
+}
diff --git a/profiles/gce/init.go b/profiles/gce/init.go
new file mode 100644
index 0000000..58a148d
--- /dev/null
+++ b/profiles/gce/init.go
@@ -0,0 +1,53 @@
+// +build linux
+
+// Package gce provides a Profile for Google Compute Engine and should be
+// used by binaries that only ever expect to be run on GCE.
+package gce
+
+import (
+ "veyron/profiles"
+
+ "veyron2"
+ "veyron2/config"
+ "veyron2/rt"
+
+ "veyron/profiles/internal/gce"
+)
+
+func init() {
+ rt.RegisterProfile(&profile{})
+}
+
+type profile struct{}
+
+func (p *profile) Name() string {
+ return "GCE"
+}
+
+func (p *profile) Runtime() string {
+ return ""
+}
+
+func (p *profile) Platform() *veyron2.Platform {
+ platform, _ := profiles.Platform()
+ return platform
+}
+
+func (p *profile) String() string {
+ return "net " + p.Platform().String()
+}
+
+func (p *profile) Init(rt veyron2.Runtime, publisher *config.Publisher) {
+ if !gce.RunningOnGCE() {
+ panic("GCE profile used on a non-GCE system")
+ }
+ go addressPublisher(rt, publisher)
+}
+
+func addressPublisher(rt veyron2.Runtime, p *config.Publisher) {
+ ch := make(chan config.Setting)
+ p.CreateStream("net", "network configuration", ch)
+ for {
+ // TODO(cnicolaou): publish local and external address..
+ }
+}
diff --git a/profiles/internal/gce/gce_linux.go b/profiles/internal/gce/gce_linux.go
new file mode 100644
index 0000000..bc89a54
--- /dev/null
+++ b/profiles/internal/gce/gce_linux.go
@@ -0,0 +1,76 @@
+// +build linux
+
+// Package gce functions to test whether the current process is running on
+// Google Compute Engine, and to extract settings from this environment.
+// Any server that knows it will only ever run on GCE can import this Profile,
+// but in most cases, other Profiles will use the utility routines provided
+// by it to test to ensure that they are running on GCE and to obtain
+// metadata directly from its service APIs.
+package gce
+
+import (
+ "fmt"
+ "io/ioutil"
+ "net"
+ "net/http"
+ "sync"
+ "time"
+)
+
+// This URL returns the external IP address assigned to the local GCE instance.
+// If a HTTP GET request fails for any reason, this is not a GCE instance. If
+// the result of the GET request doesn't contain a "Metadata-Flavor: Google"
+// header, it is also not a GCE instance. The body of the document contains the
+// external IP address, if present. Otherwise, the body is empty.
+// See https://developers.google.com/compute/docs/metadata for details.
+const url = "http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/access-configs/0/external-ip"
+
+// How long to wait for the HTTP request to return.
+const timeout = time.Second
+
+var (
+ once sync.Once
+ isGCEErr error
+ externalIP net.IP
+)
+
+// RunningOnGCE returns true if the current process is running
+// on a Google Compute Engine instance.
+func RunningOnGCE() bool {
+ once.Do(func() {
+ externalIP, isGCEErr = gceTest(url)
+ })
+ return isGCEErr == nil
+}
+
+// ExternalIPAddress returns the external IP address of this
+// Google Compute Engine instance, or nil if there is none. Must be
+// called after RunningOnGCE.
+func ExternalIPAddress() (net.IP, error) {
+ return externalIP, isGCEErr
+}
+
+func gceTest(url string) (net.IP, error) {
+ client := &http.Client{Timeout: timeout}
+ req, err := http.NewRequest("GET", url, nil)
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Add("Metadata-Flavor", "Google")
+ resp, err := client.Do(req)
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode != 200 {
+ return nil, fmt.Errorf("http error: %d", resp.StatusCode)
+ }
+ if flavor := resp.Header["Metadata-Flavor"]; len(flavor) != 1 || flavor[0] != "Google" {
+ return nil, fmt.Errorf("unexpected http header: %q", flavor)
+ }
+ body, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return nil, err
+ }
+ return net.ParseIP(string(body)), nil
+}
diff --git a/profiles/internal/gce/gce_other.go b/profiles/internal/gce/gce_other.go
new file mode 100644
index 0000000..5c8fe4a
--- /dev/null
+++ b/profiles/internal/gce/gce_other.go
@@ -0,0 +1,6 @@
+// +build !linux
+
+package gce
+
+// This package is a no-op on non-linux systems, but we still need at least
+// one file in the package to keep go happy!
diff --git a/products/lib/gce/gce_test.go b/profiles/internal/gce/gce_test.go
similarity index 66%
rename from products/lib/gce/gce_test.go
rename to profiles/internal/gce/gce_test.go
index ba2591b..585a842 100644
--- a/products/lib/gce/gce_test.go
+++ b/profiles/internal/gce/gce_test.go
@@ -1,3 +1,5 @@
+// +build linux
+
package gce
import (
@@ -47,16 +49,16 @@
defer stop()
baseURL := "http://" + addr.String()
- if isGCE, ip := googleComputeEngineTest(baseURL + "/404"); isGCE != false || ip != nil {
- t.Errorf("Unexpected result. Got %v:%v, want false:nil", isGCE, ip)
+ if ip, err := gceTest(baseURL + "/404"); err == nil || ip != nil {
+ t.Errorf("expected error, but not got nil")
}
- if isGCE, ip := googleComputeEngineTest(baseURL + "/200_not_gce"); isGCE != false || ip != nil {
- t.Errorf("Unexpected result. Got %v:%v, want false:nil", isGCE, ip)
+ if ip, err := gceTest(baseURL + "/200_not_gce"); err == nil || ip != nil {
+ t.Errorf("expected error, but not got nil")
}
- if isGCE, ip := googleComputeEngineTest(baseURL + "/gce_no_ip"); isGCE != true || ip != nil {
- t.Errorf("Unexpected result. Got %v:%v, want true:nil", isGCE, ip)
+ if ip, err := gceTest(baseURL + "/gce_no_ip"); err != nil || ip != nil {
+ t.Errorf("Unexpected result. Got (%v, %v), want nil:nil", ip, err)
}
- if isGCE, ip := googleComputeEngineTest(baseURL + "/gce_with_ip"); isGCE != true || ip.String() != "1.2.3.4" {
- t.Errorf("Unexpected result. Got %v:%v, want true:1.2.3.4", isGCE, ip)
+ if ip, err := gceTest(baseURL + "/gce_with_ip"); err != nil || ip.String() != "1.2.3.4" {
+ t.Errorf("Unexpected result. Got (%v, %v), want nil:1.2.3.4", ip, err)
}
}
diff --git a/profiles/net/gce_linux.go b/profiles/net/gce_linux.go
new file mode 100644
index 0000000..785d38e
--- /dev/null
+++ b/profiles/net/gce_linux.go
@@ -0,0 +1,40 @@
+// +build linux
+
+package net
+
+import (
+ "net"
+
+ "veyron2"
+
+ "veyron/profiles/internal/gce"
+)
+
+func handleGCE(rt veyron2.Runtime, publisher *config.Publisher) bool {
+ log := rt.Logger()
+ if gce.RunningOnGCE() {
+ var pub net.IP
+ pub = publish_addr.IP
+ if pub == nil {
+ // Determine the IP address from GCE's metadata
+ if ip, err := gce.ExternalIPAddress(); err != nil {
+ log.Infof("failed to query GCE metadata: %s", err)
+ } else {
+ // 1:1 NAT case, our network config will not change.
+ pub = ip
+ }
+ }
+ if pub == nil {
+ log.Infof("failed to determine public IP address to publish with")
+ }
+
+ ch := make(chan config.Setting)
+ defer close(ch)
+ if _, err := publisher.CreateStream(StreamName, "network configuration", ch); err != nil {
+ return nil, nil, err
+ }
+ publishInitialSettings(ch, listenProtocol, listenSpec, prevAddr)
+ return true
+ }
+ return false
+}
diff --git a/profiles/net/gce_other.go b/profiles/net/gce_other.go
new file mode 100644
index 0000000..1abfc5c
--- /dev/null
+++ b/profiles/net/gce_other.go
@@ -0,0 +1,12 @@
+// +build !linux
+
+package net
+
+import (
+ "veyron2"
+ "veyron2/config"
+)
+
+func handleGCE(veyron2.Runtime, *config.Publisher) bool {
+ return false
+}
diff --git a/profiles/net/init.go b/profiles/net/init.go
new file mode 100644
index 0000000..bad1750
--- /dev/null
+++ b/profiles/net/init.go
@@ -0,0 +1,162 @@
+// +build linux darwin
+
+// Package net provides a network-aware Profile that provides appropriate
+// options and configuration for a variety of network configurations, including
+// being behind 1-1 NATs, using dhcp and auto-configuration for being on
+// Google Compute Engine.
+//
+// The config.Publisher mechanism is used for communicating networking
+// settings to other components of the application. The Settings stream
+// is called "net" (net.StreamName) and the Settings sent over it are
+// those defined by the ipc package.
+// TODO(cnicolaou): define the settings in the ipc package, not here.
+package net
+
+import (
+ "flag"
+ "net"
+
+ "veyron2"
+ "veyron2/config"
+ "veyron2/rt"
+
+ "veyron/profiles"
+
+ // TODO(cnicolaou): move this to profiles/internal
+ "veyron/runtimes/google/lib/netconfig"
+)
+
+const (
+ StreamName = "net"
+
+ // TODO(cnicolaou): these will eventually be defined in the veyron2/ipc
+ // package.
+ ProtocolSetting = "Protocol"
+ ListenSpecSetting = "ListenSpec"
+ AddPublishAddressSetting = "AddPublishAddr"
+ RmPublishAddressSetting = "RmPublishAddr"
+)
+
+var (
+ listen_protocol string
+ listen_addr config.IPFlag
+)
+
+func init() {
+ flag.StringVar(&listen_protocol, "veyron.protocol", "tcp4", "protocol to listen with")
+ flag.Var(&listen_addr, "veyron.address", "address to listen on")
+ rt.RegisterProfile(&profile{})
+}
+
+type profile struct{}
+
+func (p *profile) Platform() *veyron2.Platform {
+ platform, _ := profiles.Platform()
+ return platform
+}
+
+func (p *profile) Name() string {
+ return "net"
+}
+
+func (p *profile) Runtime() string {
+ return ""
+}
+
+func (p *profile) String() string {
+ return "net " + p.Platform().String()
+}
+
+func (p *profile) Init(rt veyron2.Runtime, publisher *config.Publisher) {
+ log := rt.Logger()
+
+ // TODO(cnicolaou): figure out the correct heuristics for using IPv6.
+ _, _, first := firstUsableIPv4()
+ if first == nil {
+ log.Infof("failed to find any usable IP addresses at startup")
+ }
+ public := publicIP(first)
+
+ // We now know that there is an IP address to listen on, and whether
+ // it's public or private.
+
+ // Our address is private, so we test for running on GCE
+ // and for its 1:1 NAT configuration. handleGCE returns true
+ // if we are indeed running on GCE.
+ if !public && handleGCE(rt, publisher) {
+ return
+ }
+
+ // Create stream in Init function to avoid a race between any
+ // goroutines started here and consumers started after Init returns.
+ ch := make(chan config.Setting)
+ stop, err := publisher.CreateStream(StreamName, "network configuration", ch)
+ if err != nil {
+ log.Errorf("failed to create publisher: %s", err)
+ return
+ }
+ go monitorAndPublishNetworkSettings(rt, stop, ch, listen_protocol, listen_addr.IP.String())
+}
+
+func publishInitialSettings(ch chan<- config.Setting, protocol, listenSpec string, addr net.IP) {
+ for _, setting := range []config.Setting{
+ config.NewString(ProtocolSetting, "protocol to listen with", protocol),
+ config.NewString(ListenSpecSetting, "address spec to listen on", listenSpec),
+ config.NewIP(AddPublishAddressSetting, "address to publish", addr),
+ } {
+ ch <- setting
+ }
+}
+
+// monitorNetworkSettings will publish initial Settings and then
+// monitor network configuration changes and publish subsequent
+// Settings to reflect any changes detected. It will never publish an
+// RmPublishAddressSetting without first sending an AddPublishAddressSetting.
+func monitorAndPublishNetworkSettings(rt veyron2.Runtime, stop <-chan struct{},
+ ch chan<- config.Setting,
+ listenProtocol string, listenSpec string) {
+ defer close(ch)
+
+ log := rt.Logger()
+ prev4, _, prevAddr := firstUsableIPv4()
+ // prevAddr may be nil if we are currently offline.
+
+ publishInitialSettings(ch, listenProtocol, listenSpec, prevAddr)
+
+ // Start the dhcp watcher.
+ watcher, err := netconfig.NewNetConfigWatcher()
+ if err != nil {
+ log.VI(1).Infof("Failed to get new config watcher: %s", err)
+ <-stop
+ return
+ }
+
+ for {
+ select {
+ case <-watcher.Channel():
+ cur4, _, _ := ipState()
+ added := findAdded(prev4, cur4)
+ ifc, newAddr := added.first()
+ log.VI(1).Infof("new address found: %s:%s", ifc, newAddr)
+ removed := findRemoved(prev4, cur4)
+ if prevAddr == nil || (removed.has(prevAddr) && newAddr != nil) {
+ log.VI(1).Infof("address change from %s to %s:%s",
+ prevAddr, ifc, newAddr)
+ ch <- config.NewIP(AddPublishAddressSetting, "new dhcp address to publish", newAddr)
+ ch <- config.NewIP(RmPublishAddressSetting, "remove address", prevAddr)
+ prevAddr = newAddr
+ }
+ case <-stop:
+ return
+ }
+ }
+}
+
+func firstUsableIPv4() (ipAndIf, string, net.IP) {
+ v4, _, _ := publicIPState()
+ if v4.empty() {
+ v4, _, _ = ipState()
+ }
+ ifc, first := v4.first()
+ return v4, ifc, first
+}
diff --git a/profiles/net/main.go b/profiles/net/main.go
new file mode 100644
index 0000000..cf99fc1
--- /dev/null
+++ b/profiles/net/main.go
@@ -0,0 +1,30 @@
+// +build ignore
+
+package main
+
+import (
+ "fmt"
+
+ "veyron2/config"
+ "veyron2/rt"
+
+ "veyron/profiles/net"
+)
+
+func main() {
+ r := rt.Init()
+ defer r.Cleanup()
+
+ ch := make(chan config.Setting, 10)
+ p := r.Publisher()
+ settings, err := p.ForkStream(net.StreamName, ch)
+ if err != nil {
+ r.Logger().Infof("failed to fork stream: %s", err)
+ }
+ for _, setting := range settings.Latest {
+ fmt.Println("Setting: ", setting)
+ }
+ for setting := range ch {
+ fmt.Println("Setting: ", setting)
+ }
+}
diff --git a/profiles/net/net_test.go b/profiles/net/net_test.go
new file mode 100644
index 0000000..ef862dc
--- /dev/null
+++ b/profiles/net/net_test.go
@@ -0,0 +1,129 @@
+package net
+
+import (
+ "fmt"
+ "net"
+ "reflect"
+ "testing"
+)
+
+// TODO(cnicolaou): more unit tests for:
+// Monitor/Publish network settings
+// PublicIPState
+// Empty
+// Has
+// First
+// etc.
+
+func exampleIPState() {
+ ip4, ip6, err := ipState()
+ if err != nil {
+ panic("failed to find any network interfaces!")
+ }
+ for ifc, addrs := range ip4 {
+ for _, addr := range addrs {
+ fmt.Printf("ipv4: %s: %s", ifc, addr)
+ }
+ }
+ for ifc, addrs := range ip6 {
+ for _, addr := range addrs {
+ fmt.Printf("ipv6: %s: %s", ifc, addr)
+ }
+ }
+}
+
+var (
+ a = net.ParseIP("1.2.3.4")
+ b = net.ParseIP("1.2.3.5")
+ c = net.ParseIP("1.2.3.6")
+ d = net.ParseIP("1.2.3.7")
+ a6 = net.ParseIP("2001:4860:0:2001::68")
+ b6 = net.ParseIP("2001:4860:0:2001::69")
+ c6 = net.ParseIP("2001:4860:0:2001::70")
+ d6 = net.ParseIP("2001:4860:0:2001::71")
+)
+
+func TestRemoved(t *testing.T) {
+ aif := make(ipAndIf)
+ bif := make(ipAndIf)
+ aif["eth0"] = []net.IP{a, b, c, a6, b6, c6}
+ aif["eth1"] = []net.IP{a, b, c, a6, b6, c6}
+
+ // no changes.
+ got, want := findRemoved(aif, aif), ipAndIf{}
+ if !reflect.DeepEqual(got, want) {
+ t.Errorf("got %s, want %s", got, want)
+ }
+
+ // missing interfaces
+ got, want = findRemoved(aif, bif), ipAndIf{"eth0": nil, "eth1": nil}
+ if !reflect.DeepEqual(got, want) {
+ t.Errorf("got %s, want %s", got, want)
+ }
+
+ // missing some addresses on a single interface
+ bif["eth0"] = []net.IP{a, b, a6, b6}
+ got, want = findRemoved(aif, bif), ipAndIf{
+ "eth0": []net.IP{c, c6},
+ "eth1": nil,
+ }
+ if !reflect.DeepEqual(got, want) {
+ t.Errorf("got %s, want %s", got, want)
+ }
+
+ // change an address on a single interface
+ bif["eth0"] = []net.IP{a, b, c, a6, b6, c6}
+ bif["eth1"] = []net.IP{a, b, c, a6, b6, c6}
+ bif["eth1"][2] = d
+ got, want = findRemoved(aif, bif), ipAndIf{
+ "eth1": []net.IP{c},
+ }
+ if !reflect.DeepEqual(got, want) {
+ t.Errorf("got %s, want %s", got, want)
+ }
+
+ // additions
+ bif["eth0"] = []net.IP{a, b, c, a6, b6, c6, d6}
+ bif["eth1"] = []net.IP{a, b, c, a6, b6, c6, d6}
+ got, want = findRemoved(aif, bif), ipAndIf{}
+ if !reflect.DeepEqual(got, want) {
+ t.Errorf("got %s, want %s", got, want)
+ }
+}
+
+func TestAdded(t *testing.T) {
+ aif := make(ipAndIf)
+ bif := make(ipAndIf)
+ aif["eth0"] = []net.IP{a, b, c, a6, b6, c6}
+
+ // no changes.
+ got, want := findAdded(aif, aif), ipAndIf{}
+ if !reflect.DeepEqual(got, want) {
+ t.Errorf("got %s, want %s", got, want)
+ }
+
+ // added interface
+ bif["eth0"] = []net.IP{a, b, c, a6, b6, c6}
+ bif["eth1"] = []net.IP{a, b, c, a6, b6, c6}
+ got, want = findAdded(aif, bif), ipAndIf{
+ "eth1": []net.IP{a, b, c, a6, b6, c6}}
+ if !reflect.DeepEqual(got, want) {
+ t.Errorf("got %s, want %s", got, want)
+ }
+
+ // added some address on a single interface
+ bif["eth0"] = []net.IP{a, b, c, d, a6, b6, c6, d6}
+ delete(bif, "eth1")
+ got, want = findAdded(aif, bif), ipAndIf{
+ "eth0": []net.IP{d, d6}}
+ if !reflect.DeepEqual(got, want) {
+ t.Errorf("got %s, want %s", got, want)
+ }
+
+ // removals
+ bif["eth0"] = []net.IP{a, b, c, b6}
+ got, want = findAdded(aif, bif), ipAndIf{}
+ if !reflect.DeepEqual(got, want) {
+ t.Errorf("got %s, want %s", got, want)
+ }
+}
diff --git a/profiles/net/util.go b/profiles/net/util.go
new file mode 100644
index 0000000..cee7e63
--- /dev/null
+++ b/profiles/net/util.go
@@ -0,0 +1,154 @@
+package net
+
+import (
+ "fmt"
+ "net"
+
+ // TODO(cnicolaou): move this out of the runtimes and into here.
+ "veyron/runtimes/google/lib/netconfig"
+)
+
+// ipAndIf is a map of interface name to the set of IP addresses available on
+// that interface.
+type ipAndIf map[string][]net.IP
+
+func (ifcs ipAndIf) String() string {
+ r := ""
+ for k, v := range ifcs {
+ r += fmt.Sprintf("(%v: %v)", k, v)
+ }
+ return r
+}
+
+// empty returns true if there are no addresses on any interfaces.
+func (ifcs ipAndIf) empty() bool {
+ for _, addrs := range ifcs {
+ if len(addrs) > 0 {
+ return false
+ }
+ }
+ return true
+}
+
+func (ifcs ipAndIf) first() (string, net.IP) {
+ for ifc, addrs := range ifcs {
+ if len(addrs) > 0 {
+ return ifc, addrs[0]
+ }
+ }
+ return "", nil
+}
+
+func (ifcs ipAndIf) has(ip net.IP) bool {
+ for _, addrs := range ifcs {
+ for _, a := range addrs {
+ if a.Equal(ip) {
+ return true
+ }
+ }
+ }
+ return false
+}
+
+// ipState returns the set of IPv4 and IPv6 addresses available to the running
+// process.
+func ipState() (ip4, ip6 ipAndIf, err error) {
+ interfaces, err := net.Interfaces()
+ if err != nil {
+ return nil, nil, err
+ }
+ ip4 = make(ipAndIf)
+ ip6 = make(ipAndIf)
+ for _, ifc := range interfaces {
+ addrs, err := ifc.Addrs()
+ if err != nil {
+ continue
+ }
+ for _, addr := range addrs {
+ if _, ok := addr.(*net.IPAddr); ok {
+ continue
+ }
+ ip := net.ParseIP(addr.String())
+ if ip == nil || ip.IsLoopback() {
+ continue
+ }
+ if t := ip.To4(); t != nil {
+ ip4[ifc.Name] = append(ip4[ifc.Name], ip)
+ }
+ if t := ip.To16(); t != nil {
+ ip6[ifc.Name] = append(ip6[ifc.Name], ip)
+ }
+ }
+ }
+ return ip4, ip6, nil
+}
+
+func filterPublic(ips ipAndIf) ipAndIf {
+ public := make(ipAndIf)
+ for ifc, addrs := range ips {
+ for _, a := range addrs {
+ if publicIP(a) {
+ public[ifc] = append(public[ifc], a)
+ }
+ }
+ }
+ return public
+}
+
+// publicIPState returns the set of IPv4 and IPv6 addresses available
+// to the running process that are publicly/globally routable.
+func publicIPState() (ipAndIf, ipAndIf, error) {
+ v4, v6, err := ipState()
+ if err != nil {
+ return nil, nil, err
+ }
+ return filterPublic(v4), filterPublic(v6), nil
+}
+
+// publicIP returns true if the supplied IP address is publicly/gobally
+// routable.
+func publicIP(ip net.IP) bool {
+ if ip == nil {
+ return false
+ }
+ return netconfig.IsGloballyRoutable(ip)
+}
+
+func diffAB(a, b ipAndIf, added bool) ipAndIf {
+ diff := make(ipAndIf)
+ for ak, av := range a {
+ if b[ak] == nil {
+ if added {
+ diff[ak] = av
+ } else {
+ diff[ak] = nil
+ }
+ continue
+ }
+ for _, v := range av {
+ found := false
+ for _, bv := range b[ak] {
+ if v.Equal(bv) {
+ found = true
+ break
+ }
+ }
+ if !found {
+ diff[ak] = append(diff[ak], v)
+ }
+ }
+ }
+ return diff
+}
+
+// findAdded returns the set of interfaces and/or addresses that are
+// present in b, but not in a - i.e. have been added.
+func findAdded(a, b ipAndIf) ipAndIf {
+ return diffAB(b, a, true)
+}
+
+// findRemoved returns the set of interfaces and/or addresses that
+// are present in a, but not in b - i.e. have been removed.
+func findRemoved(a, b ipAndIf) ipAndIf {
+ return diffAB(a, b, false)
+}
diff --git a/runtimes/google/rt/platform_darwin.go b/profiles/platform_darwin.go
similarity index 86%
rename from runtimes/google/rt/platform_darwin.go
rename to profiles/platform_darwin.go
index 3e47627..55e39a3 100644
--- a/runtimes/google/rt/platform_darwin.go
+++ b/profiles/platform_darwin.go
@@ -1,9 +1,6 @@
// +build darwin
-package rt
-
-// TODO(cnicolaou): these routines should be moved out to a library so
-// that they're usable by all profile implementations.
+package profiles
// #include <sys/utsname.h>
// #include <errno.h>
diff --git a/runtimes/google/rt/platform_linux.go b/profiles/platform_linux.go
similarity index 97%
rename from runtimes/google/rt/platform_linux.go
rename to profiles/platform_linux.go
index 0d2a322..b38e12c 100644
--- a/runtimes/google/rt/platform_linux.go
+++ b/profiles/platform_linux.go
@@ -1,6 +1,6 @@
// +build linux
-package rt
+package profiles
import (
"fmt"
diff --git a/profiles/profiles_test.go b/profiles/profiles_test.go
new file mode 100644
index 0000000..5a67c69
--- /dev/null
+++ b/profiles/profiles_test.go
@@ -0,0 +1,20 @@
+package profiles_test
+
+import (
+ "os"
+ "testing"
+
+ "veyron/profiles"
+)
+
+func TestGeneric(t *testing.T) {
+ p := profiles.New()
+
+ if got, want := p.Name(), "generic"; got != want {
+ t.Errorf("got %q, want %q", got, want)
+ }
+ hostname, _ := os.Hostname()
+ if got, want := p.Platform().Node, hostname; got != want {
+ t.Errorf("got %q, want %q", got, want)
+ }
+}
diff --git a/runtimes/google/rt/profile.go b/runtimes/google/rt/profile.go
deleted file mode 100644
index 33accf8..0000000
--- a/runtimes/google/rt/profile.go
+++ /dev/null
@@ -1,29 +0,0 @@
-package rt
-
-import (
- "veyron2"
- "veyron2/config"
-)
-
-type generic struct{}
-
-func (g *generic) Name() string {
- return "generic"
-}
-
-func (g *generic) Runtime() string {
- return veyron2.GoogleRuntimeName
-}
-
-func (g *generic) Platform() *veyron2.Platform {
- p, _ := Platform()
- return p
-}
-
-func (g *generic) Init(rt veyron2.Runtime, _ *config.Publisher) {
- rt.Logger().VI(1).Infof("%s\n", g.String())
-}
-
-func (g *generic) String() string {
- return "default generic on " + g.Platform().String()
-}
diff --git a/runtimes/google/rt/rt.go b/runtimes/google/rt/rt.go
index 01b8e85..717e05c 100644
--- a/runtimes/google/rt/rt.go
+++ b/runtimes/google/rt/rt.go
@@ -15,6 +15,7 @@
"veyron2/security"
"veyron2/vlog"
+ "veyron/profiles"
"veyron/runtimes/google/naming/namespace"
"veyron/services/mgmt/lib/exec"
)
@@ -66,7 +67,7 @@
rt.initSignalHandling()
if rt.profile == nil {
- rt.profile = &generic{}
+ rt.profile = profiles.New()
}
vlog.VI(1).Infof("Using profile %q", rt.profile.Name())
diff --git a/runtimes/google/rt/uts_str_linux_arm.go b/runtimes/google/rt/uts_str_linux_arm.go
deleted file mode 100644
index a3cee4e..0000000
--- a/runtimes/google/rt/uts_str_linux_arm.go
+++ /dev/null
@@ -1,16 +0,0 @@
-// +build linux,arm
-
-package rt
-
-// str converts the input byte slice to a string, ignoring everything following
-// a null character (including the null character).
-func utsStr(c []uint8) string {
- ret := make([]byte, 0, len(c))
- for _, v := range c {
- if v == 0 {
- break
- }
- ret = append(ret, byte(v))
- }
- return string(ret)
-}
diff --git a/runtimes/google/rt/uts_str_linux_nonarm.go b/runtimes/google/rt/uts_str_linux_nonarm.go
deleted file mode 100644
index 02b8f81..0000000
--- a/runtimes/google/rt/uts_str_linux_nonarm.go
+++ /dev/null
@@ -1,16 +0,0 @@
-// +build linux,!arm
-
-package rt
-
-// str converts the input byte slice to a string, ignoring everything following
-// a null character (including the null character).
-func utsStr(c []int8) string {
- ret := make([]byte, 0, len(c))
- for _, v := range c {
- if v == 0 {
- break
- }
- ret = append(ret, byte(v))
- }
- return string(ret)
-}