Cosmos Nicolaou | ef323db | 2014-09-07 22:13:28 -0700 | [diff] [blame] | 1 | // +build linux darwin |
| 2 | |
| 3 | // Package roaming 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 the ipc.Server implementation of the runtime and publishes |
| 10 | // the Settings it expects. |
| 11 | package roaming |
| 12 | |
| 13 | import ( |
| 14 | "flag" |
| 15 | "fmt" |
| 16 | "net" |
| 17 | |
Jiri Simsa | 519c507 | 2014-09-17 21:37:57 -0700 | [diff] [blame] | 18 | "veyron.io/veyron/veyron2" |
| 19 | "veyron.io/veyron/veyron2/config" |
| 20 | "veyron.io/veyron/veyron2/ipc" |
| 21 | "veyron.io/veyron/veyron2/rt" |
Cosmos Nicolaou | ef323db | 2014-09-07 22:13:28 -0700 | [diff] [blame] | 22 | |
Jiri Simsa | 519c507 | 2014-09-17 21:37:57 -0700 | [diff] [blame] | 23 | "veyron.io/veyron/veyron/lib/flags" |
| 24 | "veyron.io/veyron/veyron/lib/netconfig" |
| 25 | "veyron.io/veyron/veyron/lib/netstate" |
| 26 | "veyron.io/veyron/veyron/profiles" |
Cosmos Nicolaou | ef323db | 2014-09-07 22:13:28 -0700 | [diff] [blame] | 27 | ) |
| 28 | |
| 29 | const ( |
Cosmos Nicolaou | 778cb7e | 2014-09-10 15:07:43 -0700 | [diff] [blame] | 30 | SettingsStreamName = "roaming" |
Cosmos Nicolaou | ef323db | 2014-09-07 22:13:28 -0700 | [diff] [blame] | 31 | ) |
| 32 | |
| 33 | var ( |
Cosmos Nicolaou | 778cb7e | 2014-09-10 15:07:43 -0700 | [diff] [blame] | 34 | listenProtocolFlag = flags.TCPProtocolFlag{"tcp"} |
| 35 | listenAddressFlag = flags.IPHostPortFlag{Port: "0"} |
| 36 | listenProxyFlag string |
| 37 | ListenSpec *ipc.ListenSpec |
Cosmos Nicolaou | ef323db | 2014-09-07 22:13:28 -0700 | [diff] [blame] | 38 | ) |
| 39 | |
| 40 | func init() { |
| 41 | flag.Var(&listenProtocolFlag, "veyron.tcp.protocol", "protocol to listen with") |
Cosmos Nicolaou | 778cb7e | 2014-09-10 15:07:43 -0700 | [diff] [blame] | 42 | flag.Var(&listenAddressFlag, "veyron.tcp.address", "address to listen on") |
| 43 | flag.StringVar(&listenProxyFlag, "veyron.proxy", "", "proxy to use") |
| 44 | |
Cosmos Nicolaou | ef323db | 2014-09-07 22:13:28 -0700 | [diff] [blame] | 45 | rt.RegisterProfile(New()) |
| 46 | } |
| 47 | |
| 48 | type profile struct { |
Cosmos Nicolaou | 767b62d | 2014-09-19 13:58:40 -0700 | [diff] [blame] | 49 | gce string |
Cosmos Nicolaou | ef323db | 2014-09-07 22:13:28 -0700 | [diff] [blame] | 50 | } |
| 51 | |
| 52 | func preferredIPAddress(network string, addrs []net.Addr) (net.Addr, error) { |
| 53 | if !netstate.IsIPProtocol(network) { |
| 54 | return nil, fmt.Errorf("can't support network protocol %q", network) |
| 55 | } |
| 56 | al := netstate.AddrList(addrs).Map(netstate.ConvertToIPHost) |
| 57 | for _, predicate := range []netstate.Predicate{netstate.IsPublicUnicastIPv4, |
| 58 | netstate.IsUnicastIPv4, netstate.IsPublicUnicastIPv6} { |
| 59 | if a := al.First(predicate); a != nil { |
| 60 | return a, nil |
| 61 | } |
| 62 | } |
| 63 | return nil, fmt.Errorf("failed to find any usable address for %q", network) |
| 64 | } |
| 65 | |
| 66 | func New() veyron2.Profile { |
Cosmos Nicolaou | 767b62d | 2014-09-19 13:58:40 -0700 | [diff] [blame] | 67 | return &profile{} |
Cosmos Nicolaou | ef323db | 2014-09-07 22:13:28 -0700 | [diff] [blame] | 68 | } |
| 69 | |
| 70 | func (p *profile) Platform() *veyron2.Platform { |
| 71 | platform, _ := profiles.Platform() |
| 72 | return platform |
| 73 | } |
| 74 | |
| 75 | func (p *profile) Name() string { |
Cosmos Nicolaou | c0e4b79 | 2014-09-25 10:57:52 -0700 | [diff] [blame^] | 76 | return "roaming" + p.gce |
Cosmos Nicolaou | ef323db | 2014-09-07 22:13:28 -0700 | [diff] [blame] | 77 | } |
| 78 | |
| 79 | func (p *profile) Runtime() string { |
| 80 | return "" |
| 81 | } |
| 82 | |
| 83 | func (p *profile) String() string { |
| 84 | return p.Name() + " " + p.Platform().String() |
| 85 | } |
| 86 | |
Cosmos Nicolaou | 682d7fd | 2014-09-24 22:54:16 -0700 | [diff] [blame] | 87 | func (p *profile) Init(rt veyron2.Runtime, publisher *config.Publisher) error { |
Cosmos Nicolaou | ef323db | 2014-09-07 22:13:28 -0700 | [diff] [blame] | 88 | log := rt.Logger() |
| 89 | |
Robin Thellend | ae260e4 | 2014-09-22 14:46:43 -0700 | [diff] [blame] | 90 | ListenSpec = &ipc.ListenSpec{ |
| 91 | Protocol: listenProtocolFlag.Protocol, |
| 92 | Address: listenAddressFlag.String(), |
| 93 | Proxy: listenProxyFlag, |
| 94 | } |
| 95 | |
Cosmos Nicolaou | ef323db | 2014-09-07 22:13:28 -0700 | [diff] [blame] | 96 | state, err := netstate.GetAccessibleIPs() |
| 97 | if err != nil { |
| 98 | log.Infof("failed to determine network state") |
Cosmos Nicolaou | 682d7fd | 2014-09-24 22:54:16 -0700 | [diff] [blame] | 99 | return err |
Cosmos Nicolaou | ef323db | 2014-09-07 22:13:28 -0700 | [diff] [blame] | 100 | } |
| 101 | first := state.First(netstate.IsUnicastIP) |
| 102 | if first == nil { |
| 103 | log.Infof("failed to find any usable IP addresses at startup") |
| 104 | } |
| 105 | public := netstate.IsPublicUnicastIPv4(first) |
| 106 | |
| 107 | // We now know that there is an IP address to listen on, and whether |
| 108 | // it's public or private. |
| 109 | |
| 110 | // Our address is private, so we test for running on GCE and for its |
| 111 | // 1:1 NAT configuration. handleGCE returns a non-nil addr |
| 112 | // if we are indeed running on GCE. |
| 113 | if !public { |
| 114 | if addr := handleGCE(rt, publisher); addr != nil { |
Cosmos Nicolaou | 767b62d | 2014-09-19 13:58:40 -0700 | [diff] [blame] | 115 | ListenSpec.AddressChooser = func(string, []net.Addr) (net.Addr, error) { |
Cosmos Nicolaou | ef323db | 2014-09-07 22:13:28 -0700 | [diff] [blame] | 116 | return addr, nil |
| 117 | } |
| 118 | p.gce = "+gce" |
Cosmos Nicolaou | 682d7fd | 2014-09-24 22:54:16 -0700 | [diff] [blame] | 119 | return nil |
Cosmos Nicolaou | ef323db | 2014-09-07 22:13:28 -0700 | [diff] [blame] | 120 | } |
| 121 | } |
| 122 | |
| 123 | // Create stream in Init function to avoid a race between any |
| 124 | // goroutines started here and consumers started after Init returns. |
| 125 | ch := make(chan config.Setting) |
| 126 | stop, err := publisher.CreateStream(SettingsStreamName, "dhcp", ch) |
| 127 | if err != nil { |
| 128 | log.Errorf("failed to create publisher: %s", err) |
Cosmos Nicolaou | 682d7fd | 2014-09-24 22:54:16 -0700 | [diff] [blame] | 129 | return err |
Cosmos Nicolaou | ef323db | 2014-09-07 22:13:28 -0700 | [diff] [blame] | 130 | } |
| 131 | |
| 132 | protocol := listenProtocolFlag.Protocol |
Cosmos Nicolaou | 767b62d | 2014-09-19 13:58:40 -0700 | [diff] [blame] | 133 | ListenSpec.StreamPublisher = publisher |
| 134 | ListenSpec.StreamName = "dhcp" |
| 135 | ListenSpec.AddressChooser = preferredIPAddress |
Cosmos Nicolaou | 778cb7e | 2014-09-10 15:07:43 -0700 | [diff] [blame] | 136 | log.VI(2).Infof("Initial Network Settings: %s %s available: %s", protocol, listenAddressFlag, state) |
Cosmos Nicolaou | 767b62d | 2014-09-19 13:58:40 -0700 | [diff] [blame] | 137 | go monitorNetworkSettings(rt, stop, ch, state, ListenSpec) |
Cosmos Nicolaou | 682d7fd | 2014-09-24 22:54:16 -0700 | [diff] [blame] | 138 | return nil |
Cosmos Nicolaou | ef323db | 2014-09-07 22:13:28 -0700 | [diff] [blame] | 139 | } |
| 140 | |
Cosmos Nicolaou | ef323db | 2014-09-07 22:13:28 -0700 | [diff] [blame] | 141 | // monitorNetworkSettings will monitor network configuration changes and |
| 142 | // publish subsequent Settings to reflect any changes detected. |
| 143 | func monitorNetworkSettings(rt veyron2.Runtime, stop <-chan struct{}, |
Cosmos Nicolaou | 767b62d | 2014-09-19 13:58:40 -0700 | [diff] [blame] | 144 | ch chan<- config.Setting, prev netstate.AddrList, listenSpec *ipc.ListenSpec) { |
Cosmos Nicolaou | ef323db | 2014-09-07 22:13:28 -0700 | [diff] [blame] | 145 | defer close(ch) |
| 146 | |
| 147 | log := rt.Logger() |
| 148 | |
| 149 | // Start the dhcp watcher. |
| 150 | watcher, err := netconfig.NewNetConfigWatcher() |
| 151 | if err != nil { |
| 152 | log.VI(2).Infof("Failed to get new config watcher: %s", err) |
| 153 | // TODO(cnicolaou): add support for shutting down profiles |
| 154 | //<-stop |
| 155 | return |
| 156 | } |
| 157 | |
| 158 | for { |
| 159 | select { |
| 160 | case <-watcher.Channel(): |
| 161 | cur, err := netstate.GetAccessibleIPs() |
| 162 | if err != nil { |
| 163 | log.Errorf("failed to read network state: %s", err) |
| 164 | continue |
| 165 | } |
| 166 | removed := netstate.FindRemoved(prev, cur) |
| 167 | added := netstate.FindAdded(prev, cur) |
| 168 | log.VI(2).Infof("Previous: %d: %s", len(prev), prev) |
| 169 | log.VI(2).Infof("Current : %d: %s", len(cur), cur) |
| 170 | log.VI(2).Infof("Added : %d: %s", len(added), added) |
| 171 | log.VI(2).Infof("Removed : %d: %s", len(removed), removed) |
| 172 | if len(removed) == 0 && len(added) == 0 { |
| 173 | log.VI(2).Infof("Network event that lead to no address changes since our last 'baseline'") |
| 174 | continue |
| 175 | } |
| 176 | if len(removed) > 0 { |
| 177 | log.VI(2).Infof("Sending removed: %s", removed) |
| 178 | ch <- ipc.NewRmAddrsSetting(removed) |
| 179 | } |
| 180 | // We will always send the best currently available address |
Cosmos Nicolaou | 767b62d | 2014-09-19 13:58:40 -0700 | [diff] [blame] | 181 | if chosen, err := listenSpec.AddressChooser(listenSpec.Protocol, cur); err == nil && chosen != nil { |
Cosmos Nicolaou | ef323db | 2014-09-07 22:13:28 -0700 | [diff] [blame] | 182 | ch <- ipc.NewAddAddrsSetting([]net.Addr{chosen}) |
| 183 | } |
| 184 | prev = cur |
| 185 | // TODO(cnicolaou): add support for shutting down profiles. |
| 186 | //case <-stop: |
| 187 | // return |
| 188 | } |
| 189 | } |
| 190 | } |