github.com/mysteriumnetwork/node@v0.0.0-20240516044423-365054f76801/cmd/commands/connection/command.go (about) 1 /* 2 * Copyright (C) 2020 The "MysteriumNetwork/node" Authors. 3 * 4 * This program is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 3 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 package connection 19 20 import ( 21 "errors" 22 "fmt" 23 "os" 24 "strings" 25 "text/tabwriter" 26 "time" 27 28 "github.com/urfave/cli/v2" 29 30 "github.com/mysteriumnetwork/node/cmd/commands/cli/clio" 31 "github.com/mysteriumnetwork/node/config" 32 "github.com/mysteriumnetwork/node/config/remote" 33 "github.com/mysteriumnetwork/node/core/connection" 34 "github.com/mysteriumnetwork/node/core/connection/connectionstate" 35 "github.com/mysteriumnetwork/node/datasize" 36 "github.com/mysteriumnetwork/node/identity/registry" 37 "github.com/mysteriumnetwork/node/money" 38 tequilapi_client "github.com/mysteriumnetwork/node/tequilapi/client" 39 "github.com/mysteriumnetwork/node/tequilapi/contract" 40 "github.com/mysteriumnetwork/terms/terms-go" 41 ) 42 43 // CommandName is the name of this command 44 const CommandName = "connection" 45 46 var ( 47 flagProxyPort = cli.IntFlag{ 48 Name: "proxy", 49 Usage: "Proxy port", 50 } 51 52 flagCountry = cli.StringFlag{ 53 Name: "country", 54 Usage: "Two letter (ISO 3166-1 alpha-2) country code to filter proposals.", 55 } 56 57 flagLocationType = cli.StringFlag{ 58 Name: "location-type", 59 Usage: "Node location types to filter by eg.'hosting', 'residential', 'mobile' etc.", 60 } 61 62 flagSortType = cli.StringFlag{ 63 Name: "sort", 64 Usage: "Proposal sorting type. One of: quality, bandwidth, latency, uptime or price", 65 Value: "quality", 66 } 67 68 flagIncludeFailed = cli.BoolFlag{ 69 Name: "include-failed", 70 Usage: "Include proposals marked as test failed by monitoring agent", 71 Value: false, 72 } 73 ) 74 75 const serviceWireguard = "wireguard" 76 77 // NewCommand function creates license command. 78 func NewCommand() *cli.Command { 79 var cmd *command 80 81 return &cli.Command{ 82 Name: CommandName, 83 Usage: "Manage your connection", 84 Description: "Using the connection subcommands you can manage your connection or get additional information about it", 85 Flags: []cli.Flag{&config.FlagTequilapiAddress, &config.FlagTequilapiPort}, 86 Before: func(ctx *cli.Context) error { 87 tc, err := clio.NewTequilApiClient(ctx) 88 if err != nil { 89 return err 90 } 91 92 cfg, err := remote.NewConfig(tc) 93 if err != nil { 94 return err 95 } 96 97 cmd = &command{ 98 tequilapi: tc, 99 cfg: cfg, 100 } 101 return nil 102 }, 103 Subcommands: []*cli.Command{ 104 { 105 Name: "proposals", 106 Usage: "List all possible proposals to which you can connect", 107 Flags: []cli.Flag{&flagCountry, &flagLocationType}, 108 Action: func(ctx *cli.Context) error { 109 cmd.proposals(ctx) 110 return nil 111 }, 112 }, 113 { 114 Name: "up", 115 ArgsUsage: "[ProviderIdentityAddress]", 116 Usage: "Create a new connection", 117 Flags: []cli.Flag{&config.FlagAgreedTermsConditions, &flagCountry, &flagLocationType, &flagSortType, &flagIncludeFailed, &flagProxyPort}, 118 Action: func(ctx *cli.Context) error { 119 cmd.up(ctx) 120 return nil 121 }, 122 }, 123 { 124 Name: "down", 125 Usage: "Disconnect from your current connection", 126 Flags: []cli.Flag{&flagProxyPort}, 127 Action: func(ctx *cli.Context) error { 128 cmd.down(ctx) 129 return nil 130 }, 131 }, 132 { 133 Name: "info", 134 Usage: "Show information about your connection", 135 Flags: []cli.Flag{&flagProxyPort}, 136 Action: func(ctx *cli.Context) error { 137 cmd.info(ctx) 138 return nil 139 }, 140 }, 141 }, 142 } 143 } 144 145 type command struct { 146 tequilapi *tequilapi_client.Client 147 cfg *remote.Config 148 } 149 150 func (c *command) proposals(ctx *cli.Context) { 151 locationType := ctx.String(flagLocationType.Name) 152 locationCountry := ctx.String(flagCountry.Name) 153 if locationCountry != "" && len(locationCountry) != 2 { 154 clio.Warn("Country code must be in ISO 3166-1 alpha-2 format. Example: 'UK', 'US'") 155 return 156 } 157 158 proposals, err := c.tequilapi.ProposalsByLocationAndService(serviceWireguard, locationType, locationCountry) 159 if err != nil { 160 clio.Warn("Failed to fetch proposal list") 161 return 162 } 163 164 if len(proposals) == 0 { 165 clio.Info("No proposals found") 166 return 167 } 168 169 clio.Info("Found proposals:") 170 w := tabwriter.NewWriter(os.Stdout, 1, 1, 1, ' ', 0) 171 for _, p := range proposals { 172 fmt.Fprintln(w, proposalFormatted(&p)) 173 } 174 w.Flush() 175 } 176 177 func (c *command) down(ctx *cli.Context) { 178 status, err := c.tequilapi.ConnectionStatus(ctx.Int(flagProxyPort.Name)) 179 if err != nil { 180 clio.Warn("Could not get connection status") 181 return 182 } 183 184 if status.Status != string(connectionstate.NotConnected) { 185 if err := c.tequilapi.ConnectionDestroy(ctx.Int(flagProxyPort.Name)); err != nil { 186 clio.Warn(err) 187 return 188 } 189 } 190 191 clio.Success("Disconnected") 192 } 193 194 func (c *command) handleTOS(ctx *cli.Context) error { 195 if ctx.Bool(config.FlagAgreedTermsConditions.Name) { 196 c.acceptTOS() 197 return nil 198 } 199 200 agreed := c.cfg.GetBool(contract.TermsConsumerAgreed) 201 if !agreed { 202 return errors.New("you must agree with consumer terms of use in order to use this command") 203 } 204 205 version := c.cfg.GetString(contract.TermsVersion) 206 if version != terms.TermsVersion { 207 return fmt.Errorf("you've agreed to terms of use version %s, but version %s is required", version, terms.TermsVersion) 208 } 209 210 return nil 211 } 212 213 func (c *command) acceptTOS() { 214 t := true 215 if err := c.tequilapi.UpdateTerms(contract.TermsRequest{ 216 AgreedConsumer: &t, 217 AgreedVersion: terms.TermsVersion, 218 }); err != nil { 219 clio.Info("Failed to save terms of use agreement, you will have to re-agree on next launch") 220 } 221 } 222 223 func (c *command) up(ctx *cli.Context) { 224 if err := c.handleTOS(ctx); err != nil { 225 clio.PrintTOSError(err) 226 return 227 } 228 229 status, err := c.tequilapi.ConnectionStatus(ctx.Int(flagProxyPort.Name)) 230 if err != nil { 231 clio.Warn("Could not get connection status") 232 return 233 } 234 235 switch connectionstate.State(status.Status) { 236 case 237 // connectionstate.Connected, 238 connectionstate.Connecting, 239 connectionstate.Disconnecting, 240 connectionstate.Reconnecting: 241 242 msg := fmt.Sprintf("You can't create a new connection, you're in state '%s'", status.Status) 243 clio.Warn(msg) 244 return 245 } 246 247 providers := strings.Split(ctx.Args().First(), ",") 248 providerIDs := []string{} 249 250 for _, p := range providers { 251 if len(p) > 0 { 252 providerIDs = append(providerIDs, p) 253 } 254 } 255 256 id, err := c.tequilapi.CurrentIdentity("", "") 257 if err != nil { 258 clio.Error("Failed to get your identity") 259 return 260 } 261 262 identityStatus, err := c.tequilapi.Identity(id.Address) 263 if err != nil { 264 clio.Warn("Failed to get identity status") 265 return 266 } 267 268 if identityStatus.RegistrationStatus != registry.Registered.String() { 269 clio.Warn("Your identity is not registered, please execute `myst account register` first") 270 return 271 } 272 273 clio.Status("CONNECTING", "Creating connection from:", id.Address, "to:", providers) 274 275 connectOptions := contract.ConnectOptions{ 276 DNS: connection.DNSOptionAuto, 277 DisableKillSwitch: false, 278 ProxyPort: ctx.Int(flagProxyPort.Name), 279 } 280 hermesID, err := c.cfg.GetHermesID() 281 if err != nil { 282 clio.Error(err) 283 return 284 } 285 286 filter := contract.ConnectionCreateFilter{ 287 Providers: providerIDs, 288 CountryCode: ctx.String(flagCountry.Name), 289 IPType: ctx.String(flagLocationType.Name), 290 SortBy: ctx.String(flagSortType.Name), 291 IncludeMonitoringFailed: ctx.Bool(flagIncludeFailed.Name), 292 } 293 294 _, err = c.tequilapi.SmartConnectionCreate(id.Address, hermesID, serviceWireguard, filter, connectOptions) 295 if err != nil { 296 clio.Error("Failed to create a new connection: ", err) 297 return 298 } 299 300 clio.Success("Connected") 301 } 302 303 func (c *command) info(ctx *cli.Context) { 304 inf := newConnInfo() 305 306 id, err := c.tequilapi.CurrentIdentity("", "") 307 if err == nil { 308 inf.set(infIdentity, id.Address) 309 } 310 311 status, err := c.tequilapi.ConnectionStatus(ctx.Int(flagProxyPort.Name)) 312 if err == nil { 313 if status.Status == string(connectionstate.Connected) { 314 inf.isConnected = true 315 inf.set(infProposal, status.Proposal.String()) 316 } 317 318 inf.set(infStatus, status.Status) 319 inf.set(infSessionID, status.SessionID) 320 } 321 322 ip, err := c.tequilapi.ProxyIP(ctx.Int(flagProxyPort.Name)) 323 if err == nil { 324 inf.set(infIP, ip.IP) 325 } 326 327 location, err := c.tequilapi.ProxyLocation(ctx.Int(flagProxyPort.Name)) 328 if err == nil { 329 inf.set(infLocation, fmt.Sprintf("%s, %s (%s - %s)", location.City, location.Country, location.IPType, location.ISP)) 330 } 331 332 if status.Status != string(connectionstate.Connected) { 333 inf.printAll() 334 return 335 } 336 337 statistics, err := c.tequilapi.ConnectionStatistics(status.SessionID) 338 if err == nil { 339 inf.set(infDuration, fmt.Sprint(time.Duration(statistics.Duration)*time.Second)) 340 inf.set(infTransferred, fmt.Sprintf("%s/%s", datasize.FromBytes(statistics.BytesReceived), datasize.FromBytes(statistics.BytesSent))) 341 inf.set(infThroughput, fmt.Sprintf("%s/%s", datasize.BitSpeed(statistics.ThroughputReceived), datasize.BitSpeed(statistics.ThroughputSent))) 342 inf.set(infSpent, money.New(statistics.TokensSpent).String()) 343 } 344 345 inf.printAll() 346 }