├── .github └── workflows │ └── go.yml ├── .gitignore ├── README.md ├── go.mod ├── go.sum ├── parser.go └── utils.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | on: [push] 3 | jobs: 4 | 5 | build: 6 | name: Build 7 | runs-on: ubuntu-latest 8 | steps: 9 | 10 | - name: Set up Go 1.12 11 | uses: actions/setup-go@v1 12 | with: 13 | go-version: 1.12 14 | id: go 15 | 16 | - name: Check out code into the Go module directory 17 | uses: actions/checkout@v1 18 | 19 | - name: Get dependencies 20 | run: | 21 | go get -v -t -d ./... 22 | if [ -f Gopkg.toml ]; then 23 | curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh 24 | dep ensure 25 | fi 26 | 27 | - name: Build 28 | run: go build -v . 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | mgosniff 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mgosniff - MongoDB Wire Protocol Analysis Tools 2 | 3 | Reference: [MongoDB Wire Protocol](https://docs.mongodb.org/manual/reference/mongodb-wire-protocol/) 4 | 5 | ### Introduction 6 | 7 | Different from [mongosniff](https://docs.mongodb.org/manual/reference/program/mongosniff/), `mgosniff` acted as a tcp proxy between client and mongodb server. all client request treffic and mongodb server reply traffic will go through `mgosniff`, `mgosniff` understand the protocal, so it will do analyse and show what is going on between client and server in human-readable way. 8 | 9 | ### Install 10 | 11 | ``` 12 | go get github.com/ma6174/mgosniff 13 | ``` 14 | 15 | ## Example 16 | 17 | ##### 1. start mgosniff 18 | 19 | ```shell 20 | $ mgosniff -h 21 | Usage of mgosniff: 22 | -d string 23 | proxy to dest addr (default "127.0.0.1:27017") 24 | -l string 25 | listen port (default ":7017") 26 | -v show version 27 | $ mgosniff 28 | 2015/11/29 17:01:45 parser.go:278: mgosniff listen at :7017, proxy to mongodb server 127.0.0.1:27017 29 | ``` 30 | 31 | now mgosniff running at 0.0.0.0:7017 32 | 33 | ##### 2. connect to mgosniff and do some operation 34 | 35 | ```shell 36 | $ cat test.js 37 | printjson(db.version()) 38 | printjson(db.test.insert([{test1:1},{test2:2}])) 39 | printjson(db.test.find().toArray()) 40 | printjson(db.test.remove({test1:1})) 41 | printjson(db.test.update({test2:2},{"$inc":{"test2":1}})) 42 | printjson(db.test.find().toArray()) 43 | printjson(db.test.drop()) 44 | $ mongo localhost:7017/testdb test.js 45 | MongoDB shell version: 3.0.7 46 | connecting to: localhost:7017/testdb 47 | "3.0.7" 48 | { 49 | "writeErrors" : [ ], 50 | "writeConcernErrors" : [ ], 51 | "nInserted" : 2, 52 | "nUpserted" : 0, 53 | "nMatched" : 0, 54 | "nModified" : 0, 55 | "nRemoved" : 0, 56 | "upserted" : [ ] 57 | } 58 | [ 59 | { 60 | "_id" : ObjectId("565abe9375a6567f7febb464"), 61 | "test1" : 1 62 | }, 63 | { 64 | "_id" : ObjectId("565abe9375a6567f7febb465"), 65 | "test2" : 2 66 | } 67 | ] 68 | { "nRemoved" : 1 } 69 | { "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 } 70 | [ { "_id" : ObjectId("565abe9375a6567f7febb465"), "test2" : 3 } ] 71 | true 72 | ``` 73 | 74 | ##### 3. all request and reply showed in mgosniff log 75 | 76 | ```shell 77 | $ mgosniff 78 | 2015/11/29 17:01:45 parser.go:278: mgosniff listen at :7017, proxy to mongodb server 127.0.0.1:27017 79 | 2015/11/29 17:05:48 parser.go:226: [127.0.0.1:52117] new client connected 80 | 2015/11/29-17:05:48.941042 [127.0.0.1:52117] QUERY id:0 coll:admin.$cmd toskip:0 toret:1 flag:0 query:{"whatsmyuri":1} sel:null 81 | 2015/11/29-17:05:48.942173 [127.0.0.1:52117] REPLY to:0 flag:1000 curID:859530373936 from:0 reted:1 docs:{"ok":1,"you":"127.0.0.1:52119"} 82 | 2015/11/29-17:05:48.943920 [127.0.0.1:52117] QUERY id:1 coll:admin.$cmd toskip:0 toret:-1 flag:0 query:{"buildinfo":1} sel:null 83 | 2015/11/29-17:05:48.944697 [127.0.0.1:52117] REPLY to:1 flag:1000 curID:859530374608 from:0 reted:1 docs:{"OpenSSLVersion":"","allocator":"system","bits":64,"compilerFlags":"-Wnon-virtual-dtor -Woverloaded-virtual -std=c++11 -fPIC -fno-strict-aliasing -ggdb -pthread -Wall -Wsign-compare -Wno-unknown-pragmas -Winvalid-pch -pipe -O3 -Wno-unused-local-typedefs -Wno-unused-function -Wno-unused-private-field -Wno-deprecated-declarations -Wno-tautological-constant-out-of-range-compare -Wno-unused-const-variable -Wno-missing-braces -Wno-inconsistent-missing-override -Wno-potentially-evaluated-expression -Wno-null-conversion -mmacosx-version-min=10.11 -std=c99","debug":false,"gitVersion":"nogitversion","javascriptEngine":"V8","loaderFlags":"","maxBsonObjectSize":16777216,"ok":1,"sysInfo":"Darwin elcapitanvm.local 15.0.0 Darwin Kernel Version 15.0.0: Wed Aug 26 16:57:32 PDT 2015; root:xnu-3247.1.106~1/RELEASE_X86_64 x86_64 BOOST_LIB_VERSION=1_49","version":"3.0.7","versionArray":[3,0,7,0]} 84 | 2015/11/29-17:05:48.947665 [127.0.0.1:52117] QUERY id:2 coll:admin.$cmd toskip:0 toret:-1 flag:0 query:{"isMaster":1} sel:null 85 | 2015/11/29-17:05:48.951487 [127.0.0.1:52117] REPLY to:2 flag:1000 curID:859531149344 from:0 reted:1 docs:{"ismaster":true,"localTime":1448787948948,"maxBsonObjectSize":16777216,"maxMessageSizeBytes":48000000,"maxWireVersion":3,"maxWriteBatchSize":1000,"minWireVersion":0,"ok":1} 86 | 2015/11/29-17:05:48.955595 [127.0.0.1:52117] QUERY id:3 coll:testdb.$cmd toskip:0 toret:-1 flag:0 query:{"documents":[{"_id":"Vlq/7INZSsRmFzB4","test1":1},{"_id":"Vlq/7INZSsRmFzB5","test2":2}],"insert":"test","ordered":true} sel:null 87 | 2015/11/29-17:05:48.956259 [127.0.0.1:52117] REPLY to:3 flag:1000 curID:859530377472 from:0 reted:1 docs:{"n":2,"ok":1} 88 | 2015/11/29-17:05:48.958240 [127.0.0.1:52117] QUERY id:4 coll:testdb.test toskip:0 toret:0 flag:0 query:{} sel:null 89 | 2015/11/29-17:05:48.958764 [127.0.0.1:52117] REPLY to:4 flag:1000 curID:859530378112 from:0 reted:2 docs:[{"_id":"Vlq/7INZSsRmFzB4","test1":1},{"_id":"Vlq/7INZSsRmFzB5","test2":2}] 90 | 2015/11/29-17:05:48.960042 [127.0.0.1:52117] QUERY id:5 coll:testdb.$cmd toskip:0 toret:-1 flag:0 query:{"delete":"test","deletes":[{"limit":0,"q":{"test1":1}}],"ordered":true} sel:null 91 | 2015/11/29-17:05:48.960553 [127.0.0.1:52117] REPLY to:5 flag:1000 curID:859530379024 from:0 reted:1 docs:{"n":1,"ok":1} 92 | 2015/11/29-17:05:48.962203 [127.0.0.1:52117] QUERY id:6 coll:testdb.$cmd toskip:0 toret:-1 flag:0 query:{"ordered":true,"update":"test","updates":[{"multi":false,"q":{"test2":2},"u":{"$inc":{"test2":1}},"upsert":false}]} sel:null 93 | 2015/11/29-17:05:48.962794 [127.0.0.1:52117] REPLY to:6 flag:1000 curID:859531346320 from:0 reted:1 docs:{"n":1,"nModified":1,"ok":1} 94 | 2015/11/29-17:05:48.963472 [127.0.0.1:52117] QUERY id:7 coll:testdb.test toskip:0 toret:0 flag:0 query:{} sel:null 95 | 2015/11/29-17:05:48.963868 [127.0.0.1:52117] REPLY to:7 flag:1000 curID:859531347056 from:0 reted:1 docs:{"_id":"Vlq/7INZSsRmFzB5","test2":3} 96 | 2015/11/29-17:05:48.964370 [127.0.0.1:52117] QUERY id:8 coll:testdb.$cmd toskip:0 toret:-1 flag:0 query:{"drop":"test"} sel:null 97 | 2015/11/29-17:05:48.964970 [127.0.0.1:52117] REPLY to:8 flag:1000 curID:859531347648 from:0 reted:1 docs:{"nIndexesWas":1,"ns":"testdb.test","ok":1} 98 | 2015/11/29 17:05:48 parser.go:252: [127.0.0.1:52117] close connection:127.0.0.1:52117 99 | 2015/11/29 17:05:48 parser.go:252: [127.0.0.1:52117] close connection:127.0.0.1:27017 100 | ``` 101 | 102 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ma6174/mgosniff 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 7 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 h1:DujepqpGd1hyOd7aW59XpK7Qymp8iy83xq74fLr21is= 2 | github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= 3 | github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= 4 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 5 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 6 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 7 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 8 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 9 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 10 | -------------------------------------------------------------------------------- /parser.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/binary" 5 | "flag" 6 | "fmt" 7 | "io" 8 | "io/ioutil" 9 | "log" 10 | "net" 11 | "os" 12 | "sync" 13 | ) 14 | 15 | var ( 16 | listenAddr = flag.String("l", ":7017", "listen port") 17 | dstAddr = flag.String("d", "127.0.0.1:27017", "proxy to dest addr") 18 | isShowVer = flag.Bool("v", false, "show version") 19 | bufferPool = sync.Pool{ 20 | New: func() interface{} { 21 | return make([]byte, 4096) 22 | }, 23 | } 24 | ) 25 | 26 | const ( 27 | OP_REPLY = 1 28 | OP_MSG = 1000 29 | OP_UPDATE = 2001 30 | OP_INSERT = 2002 31 | OP_RESERVED = 2003 32 | OP_QUERY = 2004 33 | OP_GET_MORE = 2005 34 | OP_DELETE = 2006 35 | OP_KILL_CURSORS = 2007 36 | OP_COMMAND_DEPRECATED = 2008 37 | OP_COMMAND_REPLY_DEPRECATED = 2009 38 | OP_COMMAND = 2010 39 | OP_COMMAND_REPLY = 2011 40 | OP_MSG_NEW = 2013 41 | ) 42 | 43 | const version = "0.2" 44 | 45 | type MsgHeader struct { 46 | MessageLength int32 47 | RequestID int32 48 | ResponseTo int32 49 | OpCode int32 50 | } 51 | 52 | type Parser struct { 53 | Pw *io.PipeWriter 54 | RemoteAddr string 55 | isPwClosed bool 56 | } 57 | 58 | func NewParser(remoteAddr string) *Parser { 59 | pr, pw := io.Pipe() 60 | parser := &Parser{ 61 | Pw: pw, 62 | RemoteAddr: remoteAddr, 63 | } 64 | go parser.Parse(pr) 65 | return parser 66 | } 67 | 68 | func (self *Parser) Write(p []byte) (n int, err error) { 69 | if self.isPwClosed { 70 | return len(p), nil 71 | } 72 | return self.Pw.Write(p) 73 | } 74 | 75 | func (self *Parser) Close() { 76 | self.isPwClosed = true 77 | self.Pw.Close() 78 | } 79 | 80 | func (self *Parser) ParseQuery(header MsgHeader, r io.Reader) { 81 | flag := MustReadInt32(r) 82 | fullCollectionName := ReadCString(r) 83 | numberToSkip := MustReadInt32(r) 84 | numberToReturn := MustReadInt32(r) 85 | query := ToJson(ReadDocument(r)) 86 | selector := ToJson(ReadDocument(r)) 87 | fmt.Printf("%s [%s] QUERY id:%d coll:%s toskip:%d toret:%d flag:%b query:%v sel:%v\n", 88 | currentTime(), 89 | self.RemoteAddr, 90 | header.RequestID, 91 | fullCollectionName, 92 | numberToSkip, 93 | numberToReturn, 94 | flag, 95 | query, 96 | selector, 97 | ) 98 | } 99 | 100 | func (self *Parser) ParseInsert(header MsgHeader, r io.Reader) { 101 | flag := MustReadInt32(r) 102 | fullCollectionName := ReadCString(r) 103 | docs := ReadDocuments(r) 104 | var docsStr string 105 | if len(docs) == 1 { 106 | docsStr = ToJson(docs[0]) 107 | } else { 108 | docsStr = ToJson(docs) 109 | } 110 | fmt.Printf("%s [%s] INSERT id:%d coll:%s flag:%b docs:%v\n", 111 | currentTime(), self.RemoteAddr, header.RequestID, fullCollectionName, flag, docsStr) 112 | } 113 | 114 | func (self *Parser) ParseUpdate(header MsgHeader, r io.Reader) { 115 | _ = MustReadInt32(r) 116 | fullCollectionName := ReadCString(r) 117 | flag := MustReadInt32(r) 118 | selector := ToJson(ReadDocument(r)) 119 | update := ToJson(ReadDocument(r)) 120 | fmt.Printf("%s [%s] UPDATE id:%d coll:%s flag:%b sel:%v update:%v\n", 121 | currentTime(), self.RemoteAddr, header.RequestID, fullCollectionName, flag, selector, update) 122 | } 123 | 124 | func (self *Parser) ParseGetMore(header MsgHeader, r io.Reader) { 125 | _ = MustReadInt32(r) 126 | fullCollectionName := ReadCString(r) 127 | numberToReturn := MustReadInt32(r) 128 | cursorID := ReadInt64(r) 129 | fmt.Printf("%s [%s] GETMORE id:%d coll:%s toret:%d curID:%d\n", 130 | currentTime(), self.RemoteAddr, header.RequestID, fullCollectionName, numberToReturn, cursorID) 131 | } 132 | 133 | func (self *Parser) ParseDelete(header MsgHeader, r io.Reader) { 134 | _ = MustReadInt32(r) 135 | fullCollectionName := ReadCString(r) 136 | flag := MustReadInt32(r) 137 | selector := ToJson(ReadDocument(r)) 138 | fmt.Printf("%s [%s] DELETE id:%d coll:%s flag:%b sel:%v \n", 139 | currentTime(), self.RemoteAddr, header.RequestID, fullCollectionName, flag, selector) 140 | } 141 | 142 | func (self *Parser) ParseKillCursors(header MsgHeader, r io.Reader) { 143 | _ = MustReadInt32(r) 144 | numberOfCursorIDs := MustReadInt32(r) 145 | var cursorIDs []int64 146 | for { 147 | n := ReadInt64(r) 148 | if n != nil { 149 | cursorIDs = append(cursorIDs, *n) 150 | continue 151 | } 152 | break 153 | } 154 | fmt.Printf("%s [%s] KILLCURSORS id:%d numCurID:%d curIDs:%d\n", 155 | currentTime(), self.RemoteAddr, header.RequestID, numberOfCursorIDs, cursorIDs) 156 | } 157 | 158 | func (self *Parser) ParseReply(header MsgHeader, r io.Reader) { 159 | flag := MustReadInt32(r) 160 | cursorID := ReadInt64(r) 161 | startingFrom := MustReadInt32(r) 162 | numberReturned := MustReadInt32(r) 163 | docs := ReadDocuments(r) 164 | var docsStr string 165 | if len(docs) == 1 { 166 | docsStr = ToJson(docs[0]) 167 | } else { 168 | docsStr = ToJson(docs) 169 | } 170 | fmt.Printf("%s [%s] REPLY to:%d flag:%b curID:%d from:%d reted:%d docs:%v\n", 171 | currentTime(), 172 | self.RemoteAddr, 173 | header.ResponseTo, 174 | flag, 175 | cursorID, 176 | startingFrom, 177 | numberReturned, 178 | docsStr, 179 | ) 180 | } 181 | 182 | func (self *Parser) ParseMsg(header MsgHeader, r io.Reader) { 183 | msg := ReadCString(r) 184 | fmt.Printf("%s [%s] MSG %d %s\n", currentTime(), self.RemoteAddr, header.RequestID, msg) 185 | } 186 | func (self *Parser) ParseReserved(header MsgHeader, r io.Reader) { 187 | fmt.Printf("%s [%s] RESERVED header:%v data:%v\n", currentTime(), self.RemoteAddr, header.RequestID, ToJson(header)) 188 | } 189 | 190 | func (self *Parser) ParseCommandDeprecated(header MsgHeader, r io.Reader) { 191 | fmt.Printf("%s [%s] MsgHeader %v\n", currentTime(), self.RemoteAddr, ToJson(header)) 192 | // TODO: no document, current not understand 193 | _, err := io.Copy(ioutil.Discard, r) 194 | if err != nil { 195 | fmt.Printf("[%s] read failed: %v", self.RemoteAddr, err) 196 | return 197 | } 198 | } 199 | func (self *Parser) ParseCommandReplyDeprecated(header MsgHeader, r io.Reader) { 200 | fmt.Printf("%s [%s] MsgHeader %v\n", currentTime(), self.RemoteAddr, ToJson(header)) 201 | // TODO: no document, current not understand 202 | _, err := io.Copy(ioutil.Discard, r) 203 | if err != nil { 204 | fmt.Printf("[%s] read failed: %v", self.RemoteAddr, err) 205 | return 206 | } 207 | } 208 | func (self *Parser) ParseCommand(header MsgHeader, r io.Reader) { 209 | database := ReadCString(r) 210 | commandName := ReadCString(r) 211 | metadata := ToJson(ReadDocument(r)) 212 | commandArgs := ToJson(ReadDocument(r)) 213 | inputDocs := ToJson(ReadDocuments(r)) 214 | fmt.Printf("%s [%s] COMMAND id:%v db:%v meta:%v cmd:%v args:%v docs %v\n", 215 | currentTime(), 216 | self.RemoteAddr, 217 | header.RequestID, 218 | database, 219 | metadata, 220 | commandName, 221 | commandArgs, 222 | inputDocs, 223 | ) 224 | } 225 | 226 | func (self *Parser) ParseMsgNew(header MsgHeader, r io.Reader) { 227 | flags := ToJson(MustReadInt32(r)) 228 | fmt.Printf("%s [%s] MSG start id:%v flags: %v\n", 229 | currentTime(), 230 | self.RemoteAddr, 231 | header.RequestID, 232 | flags, 233 | ) 234 | for { 235 | t := ReadBytes(r, 1) 236 | if t == nil { 237 | fmt.Printf("%s [%s] MSG end id:%v \n", 238 | currentTime(), 239 | self.RemoteAddr, 240 | header.RequestID, 241 | ) 242 | break 243 | } 244 | switch t[0] { 245 | case 0: // body 246 | body := ToJson(ReadDocument(r)) 247 | checksum, _ := ReadUint32(r) 248 | fmt.Printf("%s [%s] MSG id:%v type:0 body: %v checksum:%v\n", 249 | currentTime(), 250 | self.RemoteAddr, 251 | header.RequestID, 252 | body, 253 | checksum, 254 | ) 255 | case 1: 256 | sectionSize := MustReadInt32(r) 257 | r1 := io.LimitReader(r, int64(sectionSize)) 258 | documentSequenceIdentifier := ReadCString(r1) 259 | objects := ToJson(ReadDocuments(r1)) 260 | fmt.Printf("%s [%s] MSG id:%v type:1 documentSequenceIdentifier: %v objects:%v\n", 261 | currentTime(), 262 | self.RemoteAddr, 263 | header.RequestID, 264 | documentSequenceIdentifier, 265 | objects, 266 | ) 267 | default: 268 | log.Panic(fmt.Sprint("unknown body kind:", t[0])) 269 | } 270 | } 271 | } 272 | 273 | func (self *Parser) ParseCommandReply(header MsgHeader, r io.Reader) { 274 | metadata := ToJson(ReadDocument(r)) 275 | commandReply := ToJson(ReadDocument(r)) 276 | outputDocs := ToJson(ReadDocument(r)) 277 | fmt.Printf("%s [%s] COMMANDREPLY to:%d id:%v meta:%v cmdReply:%v outputDocs:%v\n", 278 | currentTime(), self.RemoteAddr, header.ResponseTo, header.RequestID, metadata, commandReply, outputDocs) 279 | } 280 | 281 | func (self *Parser) Parse(r *io.PipeReader) { 282 | defer func() { 283 | if e := recover(); e != nil { 284 | log.Printf("[%s] parser failed, painc: %v\n", self.RemoteAddr, e) 285 | self.isPwClosed = true 286 | self.Pw.Close() 287 | } 288 | }() 289 | for { 290 | header := MsgHeader{} 291 | err := binary.Read(r, binary.LittleEndian, &header) 292 | if err != nil { 293 | if err != io.EOF { 294 | log.Printf("[%s] unexpected error:%v\n", self.RemoteAddr, err) 295 | } 296 | break 297 | } 298 | rd := io.LimitReader(r, int64(header.MessageLength-4*4)) 299 | switch header.OpCode { 300 | case OP_QUERY: 301 | self.ParseQuery(header, rd) 302 | case OP_INSERT: 303 | self.ParseInsert(header, rd) 304 | case OP_DELETE: 305 | self.ParseDelete(header, rd) 306 | case OP_UPDATE: 307 | self.ParseUpdate(header, rd) 308 | case OP_MSG: 309 | self.ParseMsg(header, rd) 310 | case OP_REPLY: 311 | self.ParseReply(header, rd) 312 | case OP_GET_MORE: 313 | self.ParseGetMore(header, rd) 314 | case OP_KILL_CURSORS: 315 | self.ParseKillCursors(header, rd) 316 | case OP_RESERVED: 317 | self.ParseReserved(header, rd) 318 | case OP_COMMAND_DEPRECATED: 319 | self.ParseCommandDeprecated(header, rd) 320 | case OP_COMMAND_REPLY_DEPRECATED: 321 | self.ParseCommandReplyDeprecated(header, rd) 322 | case OP_COMMAND: 323 | self.ParseCommand(header, rd) 324 | case OP_COMMAND_REPLY: 325 | self.ParseCommandReply(header, rd) 326 | case OP_MSG_NEW: 327 | self.ParseMsgNew(header, rd) 328 | default: 329 | log.Printf("[%s] unknown OpCode: %d", self.RemoteAddr, header.OpCode) 330 | _, err = io.Copy(ioutil.Discard, rd) 331 | if err != nil { 332 | log.Printf("[%s] read failed: %v", self.RemoteAddr, err) 333 | break 334 | } 335 | } 336 | } 337 | } 338 | 339 | func handleConn(conn net.Conn) { 340 | dst, err := net.Dial("tcp", *dstAddr) 341 | if err != nil { 342 | log.Printf("[%s] unexpected err:%v, close connection:%s\n", conn.RemoteAddr(), err, conn.RemoteAddr()) 343 | conn.Close() 344 | return 345 | } 346 | defer dst.Close() 347 | log.Printf("[%s] new client connected: %v -> %v -> %v -> %v\n", conn.RemoteAddr(), 348 | conn.RemoteAddr(), conn.LocalAddr(), dst.LocalAddr(), dst.RemoteAddr()) 349 | parser := NewParser(conn.RemoteAddr().String()) 350 | parser2 := NewParser(conn.RemoteAddr().String()) 351 | teeReader := io.TeeReader(conn, parser) 352 | teeReader2 := io.TeeReader(dst, parser2) 353 | clean := func() { 354 | conn.Close() 355 | dst.Close() 356 | parser.Close() 357 | parser2.Close() 358 | } 359 | cp := func(dst io.Writer, src io.Reader, srcAddr string) { 360 | p := bufferPool.Get().([]byte) 361 | for { 362 | n, err := src.Read(p) 363 | if err != nil { 364 | if err != io.EOF && !isClosedErr(err) { 365 | log.Printf("[%s] unexpected error:%v\n", conn.RemoteAddr(), err) 366 | } 367 | log.Printf("[%s] close connection:%s\n", conn.RemoteAddr(), srcAddr) 368 | clean() 369 | break 370 | } 371 | _, err = dst.Write(p[:n]) 372 | if err != nil { 373 | if err != io.EOF && !isClosedErr(err) { 374 | log.Printf("[%s] unexpected error:%v\n", conn.RemoteAddr(), err) 375 | } 376 | clean() 377 | break 378 | } 379 | } 380 | bufferPool.Put(p) 381 | } 382 | go cp(conn, teeReader2, dst.RemoteAddr().String()) 383 | cp(dst, teeReader, conn.RemoteAddr().String()) 384 | } 385 | 386 | func main() { 387 | log.SetFlags(log.LstdFlags | log.Lshortfile) 388 | flag.Parse() 389 | if *isShowVer { 390 | fmt.Printf("version: %s\n", version) 391 | os.Exit(0) 392 | } 393 | log.Printf("%s listen at %s, proxy to mongodb server %s\n", os.Args[0], *listenAddr, *dstAddr) 394 | ln, err := net.Listen("tcp", *listenAddr) 395 | if err != nil { 396 | log.Fatal("listen failed:", err) 397 | } 398 | for { 399 | conn, err := ln.Accept() 400 | if err != nil { 401 | log.Println("accept connection failed:", err) 402 | continue 403 | } 404 | go handleConn(conn) 405 | } 406 | } 407 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/binary" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "net" 9 | "time" 10 | 11 | "github.com/globalsign/mgo/bson" 12 | ) 13 | 14 | func MustReadInt32(r io.Reader) (n int32) { 15 | err := binary.Read(r, binary.LittleEndian, &n) 16 | if err != nil { 17 | panic(err) 18 | } 19 | return 20 | } 21 | func ReadInt32(r io.Reader) (n int32, err error) { 22 | err = binary.Read(r, binary.LittleEndian, &n) 23 | return 24 | } 25 | 26 | func ReadUint32(r io.Reader) (n uint32, err error) { 27 | err = binary.Read(r, binary.LittleEndian, &n) 28 | return 29 | } 30 | 31 | func ReadInt64(r io.Reader) *int64 { 32 | var n int64 33 | err := binary.Read(r, binary.LittleEndian, &n) 34 | if err != nil { 35 | if err == io.EOF { 36 | return nil 37 | } 38 | panic(err) 39 | } 40 | return &n 41 | } 42 | 43 | func ReadBytes(r io.Reader, n int) []byte { 44 | b := make([]byte, n) 45 | _, err := r.Read(b) 46 | if err != nil { 47 | if err == io.EOF { 48 | return nil 49 | } 50 | panic(err) 51 | } 52 | return b 53 | } 54 | 55 | func ReadCString(r io.Reader) string { 56 | var b []byte 57 | var one = make([]byte, 1) 58 | for { 59 | _, err := r.Read(one) 60 | if err != nil { 61 | panic(err) 62 | } 63 | if one[0] == '\x00' { 64 | break 65 | } 66 | b = append(b, one[0]) 67 | } 68 | return string(b) 69 | } 70 | 71 | func ReadOne(r io.Reader) []byte { 72 | docLen, err := ReadInt32(r) 73 | if err != nil { 74 | if err == io.EOF { 75 | return nil 76 | } 77 | panic(err) 78 | } 79 | buf := make([]byte, int(docLen)) 80 | binary.LittleEndian.PutUint32(buf, uint32(docLen)) 81 | if _, err := io.ReadFull(r, buf[4:]); err != nil { 82 | panic(err) 83 | } 84 | return buf 85 | } 86 | 87 | func ReadDocument(r io.Reader) (m bson.M) { 88 | if one := ReadOne(r); one != nil { 89 | err := bson.Unmarshal(one, &m) 90 | if err != nil { 91 | panic(err) 92 | } 93 | } 94 | return m 95 | } 96 | 97 | func ReadDocuments(r io.Reader) (ms []bson.M) { 98 | for { 99 | m := ReadDocument(r) 100 | if m == nil { 101 | break 102 | } 103 | ms = append(ms, m) 104 | } 105 | return 106 | } 107 | 108 | func ToJson(v interface{}) string { 109 | b, err := json.Marshal(v) 110 | if err != nil { 111 | return fmt.Sprintf("{\"error\":%s}", err.Error()) 112 | } 113 | return string(b) 114 | } 115 | 116 | func isClosedErr(err error) bool { 117 | if e, ok := err.(*net.OpError); ok { 118 | if e.Err.Error() == "use of closed network connection" { 119 | return true 120 | } 121 | } 122 | return false 123 | } 124 | 125 | func currentTime() string { 126 | layout := "2006/01/02-15:04:05.000000" 127 | return time.Now().Format(layout) 128 | } 129 | --------------------------------------------------------------------------------