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  }