github.com/gdamore/mangos@v1.4.0/macat/macat.go (about) 1 // Copyright 2018 The Mangos Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use file except in compliance with the License. 5 // You may obtain a copy of the license at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // macat implements a nanocat(1) workalike command. 16 package main 17 18 import ( 19 "bufio" 20 "crypto/tls" 21 "crypto/x509" 22 "encoding/binary" 23 "errors" 24 "fmt" 25 "io/ioutil" 26 "os" 27 "strconv" 28 "strings" 29 "time" 30 ) 31 32 import ( 33 "github.com/droundy/goopt" 34 "nanomsg.org/go-mangos" 35 "nanomsg.org/go-mangos/protocol/bus" 36 "nanomsg.org/go-mangos/protocol/pair" 37 "nanomsg.org/go-mangos/protocol/pub" 38 "nanomsg.org/go-mangos/protocol/pull" 39 "nanomsg.org/go-mangos/protocol/push" 40 "nanomsg.org/go-mangos/protocol/rep" 41 "nanomsg.org/go-mangos/protocol/req" 42 "nanomsg.org/go-mangos/protocol/respondent" 43 "nanomsg.org/go-mangos/protocol/star" 44 "nanomsg.org/go-mangos/protocol/sub" 45 "nanomsg.org/go-mangos/protocol/surveyor" 46 "nanomsg.org/go-mangos/transport/all" 47 ) 48 49 var verbose int 50 var protoSet bool 51 var proto string 52 var dialAddrs []string 53 var listenAddrs []string 54 var subscriptions []string 55 var recvTimeout = -1 56 var sendTimeout = -1 57 var sendInterval = -1 58 var sendDelay int 59 var sendData []byte 60 var printFormat string 61 var sock mangos.Socket 62 var tlscfg tls.Config 63 var certFile string 64 var keyFile string 65 var caFile string 66 var noVerifyTLS bool 67 68 func setSocket(f func() (mangos.Socket, error)) error { 69 var err error 70 if sock != nil { 71 return errors.New("protocol already selected") 72 } 73 sock, err = f() 74 if err != nil { 75 return err 76 } 77 all.AddTransports(sock) 78 return nil 79 } 80 81 func addDial(addr string) error { 82 if !strings.Contains(addr, "://") { 83 return errors.New("invalid address format") 84 } 85 dialAddrs = append(dialAddrs, addr) 86 return nil 87 } 88 89 func addListen(addr string) error { 90 if !strings.Contains(addr, "://") { 91 return errors.New("invalid address format") 92 } 93 listenAddrs = append(listenAddrs, addr) 94 return nil 95 } 96 97 func addListenIPC(path string) error { 98 return addListen("ipc://" + path) 99 } 100 101 func addDialIPC(path string) error { 102 return addDial("ipc://" + path) 103 } 104 105 func addListenLocal(port string) error { 106 return addListen("tcp://127.0.0.1:" + port) 107 } 108 109 func addDialLocal(port string) error { 110 return addDial("tcp://127.0.0.1:" + port) 111 } 112 113 func addSub(sub string) error { 114 subscriptions = append(subscriptions, sub) 115 return nil 116 } 117 118 func setSendData(data string) error { 119 if sendData != nil { 120 return errors.New("data or file already set") 121 } 122 sendData = []byte(data) 123 return nil 124 } 125 126 func setSendFile(path string) error { 127 if sendData != nil { 128 return errors.New("data or file already set") 129 } 130 f, err := os.Open(path) 131 if err != nil { 132 return err 133 } 134 defer f.Close() 135 sendData, err = ioutil.ReadAll(f) 136 if err != nil { 137 return err 138 } 139 return nil 140 } 141 142 func setFormat(f string) error { 143 if len(printFormat) > 0 { 144 return errors.New("output format already set") 145 } 146 switch f { 147 case "no": 148 case "raw": 149 case "ascii": 150 case "quoted": 151 case "msgpack": 152 default: 153 return errors.New("invalid format type") 154 } 155 printFormat = f 156 return nil 157 } 158 159 func setTLSVer(vmin uint16, vmax uint16) error { 160 if tlscfg.MinVersion != 0 || tlscfg.MaxVersion != 0 { 161 return errors.New("TLS/SSL version already set") 162 } 163 tlscfg.MinVersion = vmin 164 tlscfg.MaxVersion = vmax 165 return nil 166 } 167 168 func setCert(path string) error { 169 if len(certFile) != 0 { 170 return errors.New("certificate file already set") 171 } 172 certFile = path 173 return nil 174 } 175 176 func setKey(path string) error { 177 if len(keyFile) != 0 { 178 return errors.New("key file already set") 179 } 180 keyFile = path 181 return nil 182 } 183 184 func setCaCert(path string) error { 185 if tlscfg.RootCAs != nil { 186 return errors.New("cacert already set") 187 } 188 caFile = path 189 190 f, err := os.Open(path) 191 if err != nil { 192 return err 193 } 194 pem, err := ioutil.ReadAll(f) 195 if err != nil { 196 return err 197 } 198 tlscfg.RootCAs = x509.NewCertPool() 199 if !tlscfg.RootCAs.AppendCertsFromPEM(pem) { 200 return errors.New("unable to load CA certs") 201 } 202 tlscfg.ClientCAs = tlscfg.RootCAs 203 return nil 204 } 205 206 func fatalf(format string, v ...interface{}) { 207 fmt.Fprintln(os.Stderr, fmt.Sprintf(format, v...)) 208 os.Exit(1) 209 } 210 211 func init() { 212 goopt.NoArg([]string{"--verbose", "-v"}, "Increase verbosity", 213 func() error { 214 verbose++ 215 return nil 216 }) 217 goopt.NoArg([]string{"--silent", "-q"}, "Decrease verbosity", 218 func() error { 219 verbose-- 220 return nil 221 }) 222 223 goopt.NoArg([]string{"--push"}, "Use PUSH socket type", func() error { 224 return setSocket(push.NewSocket) 225 }) 226 goopt.NoArg([]string{"--pull"}, "Use PULL socket type", func() error { 227 return setSocket(pull.NewSocket) 228 }) 229 goopt.NoArg([]string{"--pub"}, "Use PUB socket type", func() error { 230 return setSocket(pub.NewSocket) 231 }) 232 goopt.NoArg([]string{"--sub"}, "Use SUB socket type", func() error { 233 return setSocket(sub.NewSocket) 234 }) 235 goopt.NoArg([]string{"--req"}, "Use REQ socket type", func() error { 236 return setSocket(req.NewSocket) 237 }) 238 goopt.NoArg([]string{"--rep"}, "Use REP socket type", func() error { 239 return setSocket(rep.NewSocket) 240 }) 241 goopt.NoArg([]string{"--surveyor"}, "Use SURVEYOR socket type", 242 func() error { 243 return setSocket(surveyor.NewSocket) 244 }) 245 goopt.NoArg([]string{"--respondent"}, "Use RESPONDENT socket type", 246 func() error { 247 return setSocket(respondent.NewSocket) 248 }) 249 goopt.NoArg([]string{"--bus"}, "Use BUS socket type", func() error { 250 return setSocket(bus.NewSocket) 251 }) 252 goopt.NoArg([]string{"--pair"}, "Use PAIR socket type", func() error { 253 return setSocket(pair.NewSocket) 254 }) 255 goopt.NoArg([]string{"--star"}, "Use STAR socket type", func() error { 256 return setSocket(star.NewSocket) 257 }) 258 goopt.ReqArg([]string{"--bind"}, "ADDR", "Bind socket to ADDR", 259 addListen) 260 goopt.ReqArg([]string{"--connect"}, "ADDR", "Connect socket to ADDR", 261 addDial) 262 goopt.ReqArg([]string{"--bind-ipc", "-X"}, "PATH", 263 "Bind socket to IPC PATH", addListenIPC) 264 goopt.ReqArg([]string{"--connect-ipc", "-x"}, "PATH", 265 "Connect socket to IPC PATH", addDialIPC) 266 goopt.ReqArg([]string{"--bind-local", "-L"}, "PORT", 267 "Bind socket to TCP localhost PORT", addListenLocal) 268 goopt.ReqArg([]string{"--connect-local", "-l"}, "PORT", 269 "Connect socket to TCP localhost PORT", addDialLocal) 270 goopt.ReqArg([]string{"--subscribe"}, "PREFIX", 271 "Subcribe to PREFIX (default is wildcard)", addSub) 272 goopt.ReqArg([]string{"--recv-timeout"}, "SEC", "Set receive timeout", 273 func(to string) error { 274 var err error 275 recvTimeout, err = strconv.Atoi(to) 276 if err != nil { 277 return errors.New("value not an integer") 278 } 279 return nil 280 }) 281 goopt.ReqArg([]string{"--send-timeout"}, "SEC", "Set send timeout", 282 func(to string) error { 283 var err error 284 if sendTimeout, err = strconv.Atoi(to); err != nil { 285 return errors.New("value not an integer") 286 } 287 return nil 288 }) 289 goopt.ReqArg([]string{"--send-delay", "-d"}, "SEC", 290 "Set initial send delay", 291 func(to string) error { 292 var err error 293 if sendDelay, err = strconv.Atoi(to); err != nil { 294 return errors.New("value not an integer") 295 } 296 return nil 297 }) 298 goopt.NoArg([]string{"--raw"}, "Raw output, no delimiters", 299 func() error { 300 return setFormat("raw") 301 }) 302 goopt.NoArg([]string{"--ascii", "-A"}, "ASCII output, one per line", 303 func() error { 304 return setFormat("ascii") 305 }) 306 goopt.NoArg([]string{"--quoted", "-Q"}, "Quoted output, one per line", 307 func() error { 308 return setFormat("quoted") 309 }) 310 goopt.NoArg([]string{"--msgpack"}, 311 "Msgpacked binay output (see msgpack.org)", 312 func() error { 313 return setFormat("msgpack") 314 }) 315 316 goopt.ReqArg([]string{"--interval", "-i"}, "SEC", 317 "Send DATA every SEC seconds", 318 func(to string) error { 319 var err error 320 if sendInterval, err = strconv.Atoi(to); err != nil { 321 return errors.New("value not an integer") 322 } 323 return nil 324 }) 325 326 goopt.ReqArg([]string{"--data", "-D"}, "DATA", "Data to send", 327 setSendData) 328 goopt.ReqArg([]string{"--file", "-F"}, "FILE", "Send contents of FILE", 329 setSendFile) 330 331 goopt.ReqArg([]string{"--cert", "-E"}, "FILE", 332 "Use certificate in FILE for SSL/TLS", setCert) 333 goopt.ReqArg([]string{"--key"}, "FILE", 334 "Use private key in FILE for SSL/TLS", setKey) 335 goopt.ReqArg([]string{"--cacert"}, "FILE", 336 "Use CA certicate(s) in FILE for SSL/TLS", setCaCert) 337 goopt.NoArg([]string{"--insecure", "-k"}, 338 "Do not validate TLS/SSL peer certificate", 339 func() error { 340 noVerifyTLS = true 341 return nil 342 }) 343 goopt.Description = func() string { 344 return `The macat command is a command-line interface to 345 send and receive 346 data via the mangos implementation of the SP (nanomsg) protocols. It is 347 designed to be suitable for use as a drop-in replacement for nanocat(1).` 348 } 349 350 goopt.Author = "Garrett D'Amore" 351 352 goopt.Suite = "mangos" 353 354 goopt.Summary = "command line interface to the mangos messaging library" 355 356 } 357 358 func printMsg(msg *mangos.Message) { 359 if printFormat == "no" { 360 return 361 } 362 bw := bufio.NewWriter(os.Stdout) 363 switch printFormat { 364 case "raw": 365 bw.Write(msg.Body) 366 case "ascii": 367 for i := 0; i < len(msg.Body); i++ { 368 if strconv.IsPrint(rune(msg.Body[i])) { 369 bw.WriteByte(msg.Body[i]) 370 } else { 371 bw.WriteByte('.') 372 } 373 } 374 bw.WriteString("\n") 375 case "quoted": 376 for i := 0; i < len(msg.Body); i++ { 377 switch msg.Body[i] { 378 case '\n': 379 bw.WriteString("\\n") 380 case '\r': 381 bw.WriteString("\\r") 382 case '\\': 383 bw.WriteString("\\\\") 384 case '"': 385 bw.WriteString("\\\"") 386 default: 387 if strconv.IsPrint(rune(msg.Body[i])) { 388 bw.WriteByte(msg.Body[i]) 389 } else { 390 bw.WriteString(fmt.Sprintf("\\x%02x", 391 msg.Body[i])) 392 } 393 } 394 } 395 bw.WriteString("\n") 396 397 case "msgpack": 398 enc := make([]byte, 5) 399 switch { 400 case len(msg.Body) < 256: 401 enc = enc[:2] 402 enc[0] = 0xc4 403 enc[1] = byte(len(msg.Body)) 404 405 case len(msg.Body) < 65536: 406 enc = enc[:3] 407 enc[0] = 0xc5 408 binary.BigEndian.PutUint16(enc[1:], uint16(len(msg.Body))) 409 default: 410 enc = enc[:5] 411 enc[0] = 0xc6 412 binary.BigEndian.PutUint32(enc[1:], uint32(len(msg.Body))) 413 } 414 bw.Write(enc) 415 bw.Write(msg.Body) 416 } 417 bw.Flush() 418 } 419 420 func recvLoop(sock mangos.Socket) { 421 for { 422 msg, err := sock.RecvMsg() 423 switch err { 424 case mangos.ErrProtoState: 425 return 426 case mangos.ErrRecvTimeout: 427 return 428 case nil: 429 default: 430 fatalf("RecvMsg failed: %v", err) 431 } 432 printMsg(msg) 433 msg.Free() 434 } 435 } 436 437 func sendLoop(sock mangos.Socket) { 438 if sendData == nil { 439 fatalf("No data to send!") 440 } 441 for { 442 msg := mangos.NewMessage(len(sendData)) 443 msg.Body = append(msg.Body, sendData...) 444 err := sock.SendMsg(msg) 445 446 if err != nil { 447 fatalf("SendMsg failed: %v", err) 448 } 449 450 if sendInterval >= 0 { 451 time.Sleep(time.Duration(sendInterval) * time.Second) 452 } else { 453 break 454 } 455 } 456 } 457 458 func sendRecvLoop(sock mangos.Socket) { 459 for { 460 msg := mangos.NewMessage(len(sendData)) 461 msg.Body = append(msg.Body, sendData...) 462 err := sock.SendMsg(msg) 463 464 if err != nil { 465 fatalf("SendMsg failed: %v", err) 466 } 467 468 if sendInterval < 0 { 469 recvLoop(sock) 470 return 471 } 472 473 now := time.Now() 474 475 // maximum wait time is upper bound of recvTimeout and 476 // sendInterval 477 478 if recvTimeout < 0 || recvTimeout > sendInterval { 479 sock.SetOption(mangos.OptionRecvDeadline, 480 time.Second*time.Duration(sendInterval)) 481 } 482 msg, err = sock.RecvMsg() 483 switch err { 484 case mangos.ErrRecvTimeout: 485 case nil: 486 printMsg(msg) 487 msg.Free() 488 default: 489 fatalf("RecvMsg failed: %v", err) 490 } 491 time.Sleep((time.Second * time.Duration(sendInterval)) - 492 time.Now().Sub(now)) 493 } 494 } 495 496 func replyLoop(sock mangos.Socket) { 497 if sendData == nil { 498 fatalf("No data to send!") 499 } 500 for { 501 msg, err := sock.RecvMsg() 502 switch err { 503 case mangos.ErrRecvTimeout: 504 return 505 case nil: 506 default: 507 fatalf("RecvMsg failed: %v", err) 508 } 509 printMsg(msg) 510 msg.Free() 511 512 msg = mangos.NewMessage(len(sendData)) 513 msg.Body = append(msg.Body, sendData...) 514 err = sock.SendMsg(msg) 515 516 if err != nil { 517 fatalf("SendMsg failed: %v", err) 518 } 519 } 520 } 521 522 func cleanup() { 523 if sock != nil { 524 sock.Close() 525 } 526 } 527 528 func main() { 529 defer cleanup() 530 531 goopt.Parse(nil) 532 533 if len(certFile) != 0 { 534 if len(keyFile) == 0 { 535 keyFile = certFile 536 } 537 c, e := tls.LoadX509KeyPair(certFile, keyFile) 538 if e != nil { 539 fatalf("Failed loading cert/key: %v", e) 540 } 541 tlscfg.Certificates = make([]tls.Certificate, 0, 1) 542 tlscfg.Certificates = append(tlscfg.Certificates, c) 543 } 544 if tlscfg.RootCAs != nil { 545 tlscfg.ClientAuth = tls.RequireAndVerifyClientCert 546 tlscfg.InsecureSkipVerify = false 547 } else { 548 tlscfg.ClientAuth = tls.NoClientCert 549 tlscfg.InsecureSkipVerify = true 550 } 551 552 if sock == nil { 553 fatalf("Protocol not specified.") 554 } 555 556 if len(listenAddrs) == 0 && len(dialAddrs) == 0 { 557 fatalf("No address specified.") 558 } 559 560 if sock.GetProtocol().Number() != mangos.ProtoSub { 561 if len(subscriptions) > 0 { 562 fatalf("Subscription only valid with SUB protocol.") 563 } 564 } else { 565 if len(subscriptions) > 0 { 566 for i := range subscriptions { 567 err := sock.SetOption(mangos.OptionSubscribe, 568 []byte(subscriptions[i])) 569 if err != nil { 570 fatalf("Can't subscribe: %v", err) 571 } 572 } 573 } else { 574 err := sock.SetOption(mangos.OptionSubscribe, []byte{}) 575 if err != nil { 576 fatalf("Can't wild card subscribe: %v", err) 577 } 578 } 579 } 580 581 for i := range listenAddrs { 582 var opts = make(map[string]interface{}) 583 584 // TLS addresses require a certificate to be supplied. 585 if strings.HasPrefix(listenAddrs[i], "tls") { 586 if len(tlscfg.Certificates) == 0 { 587 fatalf("No server certificate specified.") 588 } 589 if tlscfg.InsecureSkipVerify && !noVerifyTLS { 590 fatalf("No CA certificate specified.") 591 } 592 opts[mangos.OptionTLSConfig] = &tlscfg 593 } 594 err := sock.ListenOptions(listenAddrs[i], opts) 595 if err != nil { 596 fatalf("Bind(%s): %v", listenAddrs[i], err) 597 } 598 } 599 600 for i := range dialAddrs { 601 var opts = make(map[string]interface{}) 602 603 if strings.HasPrefix(dialAddrs[i], "tls") { 604 if tlscfg.InsecureSkipVerify && !noVerifyTLS { 605 fatalf("No CA certificate specified.") 606 } 607 opts[mangos.OptionTLSConfig] = &tlscfg 608 } 609 err := sock.DialOptions(dialAddrs[i], opts) 610 if err != nil { 611 fatalf("Dial(%s): %v", dialAddrs[i], err) 612 } 613 } 614 615 // XXX: fugly hack - work around TCP slow start 616 time.Sleep(time.Millisecond * 20) 617 time.Sleep(time.Second * time.Duration(sendDelay)) 618 619 // Start main processing 620 switch sock.GetProtocol().Number() { 621 622 case mangos.ProtoPull: 623 fallthrough 624 case mangos.ProtoSub: 625 recvLoop(sock) 626 627 case mangos.ProtoPush: 628 fallthrough 629 case mangos.ProtoPub: 630 sendLoop(sock) 631 632 case mangos.ProtoPair: 633 fallthrough 634 case mangos.ProtoStar: 635 fallthrough 636 case mangos.ProtoBus: 637 if sendData != nil { 638 sendRecvLoop(sock) 639 } else { 640 recvLoop(sock) 641 } 642 643 case mangos.ProtoSurveyor: 644 fallthrough 645 case mangos.ProtoReq: 646 sendRecvLoop(sock) 647 648 case mangos.ProtoRep: 649 fallthrough 650 case mangos.ProtoRespondent: 651 if sendData != nil { 652 replyLoop(sock) 653 } else { 654 recvLoop(sock) 655 } 656 657 default: 658 fatalf("Unknown protocol!") 659 } 660 }