Cosmos Nicolaou | 6c6fa11 | 2014-08-19 13:22:33 -0700 | [diff] [blame] | 1 | // +build linux darwin |
| 2 | |
| 3 | // Package net provides a network-aware Profile that provides appropriate |
| 4 | // options and configuration for a variety of network configurations, including |
| 5 | // being behind 1-1 NATs, using dhcp and auto-configuration for being on |
| 6 | // Google Compute Engine. |
| 7 | // |
| 8 | // The config.Publisher mechanism is used for communicating networking |
| 9 | // settings to other components of the application. The Settings stream |
| 10 | // is called "net" (net.StreamName) and the Settings sent over it are |
| 11 | // those defined by the ipc package. |
| 12 | // TODO(cnicolaou): define the settings in the ipc package, not here. |
| 13 | package net |
| 14 | |
| 15 | import ( |
| 16 | "flag" |
| 17 | "net" |
| 18 | |
| 19 | "veyron2" |
| 20 | "veyron2/config" |
| 21 | "veyron2/rt" |
| 22 | |
| 23 | "veyron/profiles" |
| 24 | |
| 25 | // TODO(cnicolaou): move this to profiles/internal |
| 26 | "veyron/runtimes/google/lib/netconfig" |
| 27 | ) |
| 28 | |
| 29 | const ( |
| 30 | StreamName = "net" |
| 31 | |
| 32 | // TODO(cnicolaou): these will eventually be defined in the veyron2/ipc |
| 33 | // package. |
| 34 | ProtocolSetting = "Protocol" |
| 35 | ListenSpecSetting = "ListenSpec" |
| 36 | AddPublishAddressSetting = "AddPublishAddr" |
| 37 | RmPublishAddressSetting = "RmPublishAddr" |
| 38 | ) |
| 39 | |
| 40 | var ( |
| 41 | listen_protocol string |
| 42 | listen_addr config.IPFlag |
| 43 | ) |
| 44 | |
| 45 | func init() { |
| 46 | flag.StringVar(&listen_protocol, "veyron.protocol", "tcp4", "protocol to listen with") |
| 47 | flag.Var(&listen_addr, "veyron.address", "address to listen on") |
| 48 | rt.RegisterProfile(&profile{}) |
| 49 | } |
| 50 | |
| 51 | type profile struct{} |
| 52 | |
| 53 | func (p *profile) Platform() *veyron2.Platform { |
| 54 | platform, _ := profiles.Platform() |
| 55 | return platform |
| 56 | } |
| 57 | |
| 58 | func (p *profile) Name() string { |
| 59 | return "net" |
| 60 | } |
| 61 | |
| 62 | func (p *profile) Runtime() string { |
| 63 | return "" |
| 64 | } |
| 65 | |
| 66 | func (p *profile) String() string { |
| 67 | return "net " + p.Platform().String() |
| 68 | } |
| 69 | |
| 70 | func (p *profile) Init(rt veyron2.Runtime, publisher *config.Publisher) { |
| 71 | log := rt.Logger() |
| 72 | |
| 73 | // TODO(cnicolaou): figure out the correct heuristics for using IPv6. |
| 74 | _, _, first := firstUsableIPv4() |
| 75 | if first == nil { |
| 76 | log.Infof("failed to find any usable IP addresses at startup") |
| 77 | } |
| 78 | public := publicIP(first) |
| 79 | |
| 80 | // We now know that there is an IP address to listen on, and whether |
| 81 | // it's public or private. |
| 82 | |
| 83 | // Our address is private, so we test for running on GCE |
| 84 | // and for its 1:1 NAT configuration. handleGCE returns true |
| 85 | // if we are indeed running on GCE. |
| 86 | if !public && handleGCE(rt, publisher) { |
| 87 | return |
| 88 | } |
| 89 | |
| 90 | // Create stream in Init function to avoid a race between any |
| 91 | // goroutines started here and consumers started after Init returns. |
| 92 | ch := make(chan config.Setting) |
| 93 | stop, err := publisher.CreateStream(StreamName, "network configuration", ch) |
| 94 | if err != nil { |
| 95 | log.Errorf("failed to create publisher: %s", err) |
| 96 | return |
| 97 | } |
| 98 | go monitorAndPublishNetworkSettings(rt, stop, ch, listen_protocol, listen_addr.IP.String()) |
| 99 | } |
| 100 | |
| 101 | func publishInitialSettings(ch chan<- config.Setting, protocol, listenSpec string, addr net.IP) { |
| 102 | for _, setting := range []config.Setting{ |
| 103 | config.NewString(ProtocolSetting, "protocol to listen with", protocol), |
| 104 | config.NewString(ListenSpecSetting, "address spec to listen on", listenSpec), |
| 105 | config.NewIP(AddPublishAddressSetting, "address to publish", addr), |
| 106 | } { |
| 107 | ch <- setting |
| 108 | } |
| 109 | } |
| 110 | |
| 111 | // monitorNetworkSettings will publish initial Settings and then |
| 112 | // monitor network configuration changes and publish subsequent |
| 113 | // Settings to reflect any changes detected. It will never publish an |
| 114 | // RmPublishAddressSetting without first sending an AddPublishAddressSetting. |
| 115 | func monitorAndPublishNetworkSettings(rt veyron2.Runtime, stop <-chan struct{}, |
| 116 | ch chan<- config.Setting, |
| 117 | listenProtocol string, listenSpec string) { |
| 118 | defer close(ch) |
| 119 | |
| 120 | log := rt.Logger() |
| 121 | prev4, _, prevAddr := firstUsableIPv4() |
| 122 | // prevAddr may be nil if we are currently offline. |
| 123 | |
| 124 | publishInitialSettings(ch, listenProtocol, listenSpec, prevAddr) |
| 125 | |
| 126 | // Start the dhcp watcher. |
| 127 | watcher, err := netconfig.NewNetConfigWatcher() |
| 128 | if err != nil { |
| 129 | log.VI(1).Infof("Failed to get new config watcher: %s", err) |
| 130 | <-stop |
| 131 | return |
| 132 | } |
| 133 | |
| 134 | for { |
| 135 | select { |
| 136 | case <-watcher.Channel(): |
| 137 | cur4, _, _ := ipState() |
| 138 | added := findAdded(prev4, cur4) |
| 139 | ifc, newAddr := added.first() |
| 140 | log.VI(1).Infof("new address found: %s:%s", ifc, newAddr) |
| 141 | removed := findRemoved(prev4, cur4) |
| 142 | if prevAddr == nil || (removed.has(prevAddr) && newAddr != nil) { |
| 143 | log.VI(1).Infof("address change from %s to %s:%s", |
| 144 | prevAddr, ifc, newAddr) |
| 145 | ch <- config.NewIP(AddPublishAddressSetting, "new dhcp address to publish", newAddr) |
| 146 | ch <- config.NewIP(RmPublishAddressSetting, "remove address", prevAddr) |
| 147 | prevAddr = newAddr |
| 148 | } |
| 149 | case <-stop: |
| 150 | return |
| 151 | } |
| 152 | } |
| 153 | } |
| 154 | |
| 155 | func firstUsableIPv4() (ipAndIf, string, net.IP) { |
| 156 | v4, _, _ := publicIPState() |
| 157 | if v4.empty() { |
| 158 | v4, _, _ = ipState() |
| 159 | } |
| 160 | ifc, first := v4.first() |
| 161 | return v4, ifc, first |
| 162 | } |