• Packetbeat协议扩展开发教程(3)


    前面介绍了Packetbeat的项目结构,今天终于要开始写代码了,想想还是有点小激动呢。(你快点吧,拖半天了)
    网络传输两大协议TCP和UDP,我们的所有协议都不离这两种,HTTP、MySQL走的是TCP传输协议,DNS走的是UDP协议,在Packetbeat里面,实现一个自己的协议非常简单,继承并实现这两者对应的接口就行了,我们看一下长什么样:
    打开一个现有的UDP和HTTP协议接口定义:

    /~/Go/src/github.com/elastic/beats/packetbeat/protos/protos.go

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. // Functions to be exported by a protocol plugin  
    2. type ProtocolPlugin interface {  
    3.     // Called to initialize the Plugin  
    4.     Init(test_mode bool, results publisher.Client) error  
    5.    
    6.     // Called to return the configured ports  
    7.     GetPorts() []int  
    8. }  
    9.    
    10. type TcpProtocolPlugin interface {  
    11.     ProtocolPlugin  
    12.    
    13.     // Called when TCP payload data is available for parsing.  
    14.     Parse(pkt *Packet, tcptuple *common.TcpTuple,  
    15.         dir uint8, private ProtocolData) ProtocolData  
    16.    
    17.     // Called when the FIN flag is seen in the TCP stream.  
    18.     ReceivedFin(tcptuple *common.TcpTuple, dir uint8,  
    19.         private ProtocolData) ProtocolData  
    20.    
    21.     // Called when a packets are missing from the tcp  
    22.     // stream.  
    23.     GapInStream(tcptuple *common.TcpTuple, dir uint8, nbytes int,  
    24.         private ProtocolData) (priv ProtocolData, drop bool)  
    25.    
    26.     // ConnectionTimeout returns the per stream connection timeout.  
    27.     // Return <=0 to set default tcp module transaction timeout.  
    28.     ConnectionTimeout() time.Duration  
    29. }  
    30.    
    31. type UdpProtocolPlugin interface {  
    32.     ProtocolPlugin  
    33.    
    34.     // ParseUdp is invoked when UDP payload data is available for parsing.  
    35.     ParseUdp(pkt *Packet)  
    36. }  


    TcpProtocolPlugin:TCP协议插件的接口定义,依次是:Parse() 解析Packet,ReceivedFin()处理TCP断开连接,GapInStream()处理空包丢包,ConnectionTimeout()超时时间;

    UdpProtocolPlugin: UDP协议的接口定义,UDP协议是不需要握手和保障数据可靠性的,扔出去就结束,速度快,不保证数据可靠送达,所以只有ParseUdp一个方法需要实现,比较简单;

    ProtocolPlugin:TCP和UDP都需要实现ProtocolPlugin的基础接口,其实就定义了获取端口和初始化接口。

    请问:Packetbeat怎么工作的?

    回答:每一个协议都有一个固定的端口用于通信,你要做的事情就是定义协议端口,然后按协议是TCP还是UDP来实现对应的接口,Packetbeat将会截获指定端口的数据包(Packet),然后如果交给你定义的方法来进行解析,TCP是Parse,UDP是ParseUdp,都在上面的接口定义好的,然后将解析出来的结构化数据封装成Json,然后扔给Elasticsearch,后续的就的如何对这些数据做一些有趣的分析和应用了。

    貌似很简单嘛!

    进入每个端口的数据包,我们假设是一个自来水管,拧开80端口,哗啦啦出来的全是HTTP请求的数据包,Packetbeat里面Http协议监听的是80端口啊,所有这些包统统都交给Packetbeat里面的Http协议模块来进行解析,Http协议会一个个的检查这些数据包,也就是每个数据包都会调用一次Parse接口,到这里提到了传过来一个Packet,我们看看它的数据结构长什么样?

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. type Packet struct {  
    2.     Ts      time.Time  
    3.     Tuple   common.IpPortTuple  
    4.     Payload []byte  
    5. }  


    Packet结构简单,
    Ts是收到数据包的时间戳;
    Tuple是一个来源IP+来源端口和目的IP+目的端口的元组;
    Payload就是这个包里面的传输的有用的数据,应用层的字节数据,不包括IP和TCP/UDP头信息,是不是处理起来简单许多。
    首选我们确定SMTP协议的配置,每个协议在packetbeat.yml的protocol下面都应该有一个配置节点,如下:

    [html] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. protocols:  
    2.   smtp:  
    3.     # Configure the ports where to listen for Smtp traffic. You can disable  
    4.     # the Smtp protocol by commenting out the list of ports.  
    5.     ports: [25]  


    还需要在对应的config类文件:packetbeat/config/config.go,增加SMTP的结构体,目前只支持一个端口参数,继承基类ProtocolCommon就行,如下:

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. git diff config/config.go  
    2. @@ -42,6 +42,7 @@ type Protocols struct {  
    3.         Pgsql    Pgsql  
    4.         Redis    Redis  
    5.         Thrift   Thrift  
    6. +       Smtp     Smtp  
    7.  }  
    8.    
    9.  type Dns struct {  
    10. @@ -118,5 +119,9 @@ type Redis struct {  
    11.         Send_response *bool  
    12.  }  
    13.    
    14. +type Smtp struct {  
    15. +   ProtocolCommon        `yaml:",inline"`  
    16. +}  
    17. +  
    18.  // Config Singleton  
    19.  var ConfigSingleton Config  


    在protos文件夹下面,新增smtp目录,并新增空白文件smtp.go,路径:packetbeat/protos/smtp/smtp.go,这里就是解析SMTP协议的地方,也是我们扩展协议的主要的工作。

    [html] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. ...TODO...  


    修改protos/protos.go,增加SMTP协议枚举,这里记得保证顺序一致,并且protocol名称必须和配置的节点名称一致,如这里都是smtp。

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. git diff protos/protos.go  
    2. @@ -103,6 +103,7 @@ const (  
    3.         MongodbProtocol  
    4.         DnsProtocol  
    5.         MemcacheProtocol  
    6. +       SmtpProtocol  
    7.  )  
    8.    
    9.  // Protocol names  
    10. @@ -116,6 +117,7 @@ var ProtocolNames = []string{  
    11.         "mongodb",  
    12.         "dns",  
    13.         "memcache",  
    14. +       "smtp",  
    15.  }  


    继续修改packetbeat.go主文件,允许SMTP协议并加载。

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. git diff packetbeat.go  
    2. @@ -27,6 +27,7 @@ import (  
    3.         "github.com/elastic/packetbeat/protos/tcp"  
    4.         "github.com/elastic/packetbeat/protos/thrift"  
    5.         "github.com/elastic/packetbeat/protos/udp"  
    6. +       "github.com/elastic/packetbeat/protos/smtp"  
    7.         "github.com/elastic/packetbeat/sniffer"  
    8.  )  
    9.    
    10. @@ -43,6 +44,7 @@ var EnabledProtocolPlugins map[protos.Protocol]protos.ProtocolPlugin = map[proto  
    11.         protos.ThriftProtocol:   new(thrift.Thrift),  
    12.         protos.MongodbProtocol:  new(mongodb.Mongodb),  
    13.         protos.DnsProtocol:      new(dns.Dns),  
    14. +       protos.SmtpProtocol:      new(smtp.Smtp),  
    15.  }  


    做完上面一系列修改之后,一个空白的SMTP协议的插件的架子就搭好了,并且插件也注册到了Packetbeat里面了,接下来我们再把packetbeat/protos/smtp/smtp.go按照TCPplugin接口的要求实现一下。

    说实话TCP处理起来很难,开始之前,我们先明确几个概念,TCP协议是有状态的,并且是流式的,我们关注的是七层应用层的消息,如HTTP里面的一个HTTP请求和返回,但是TCP底层都是一系列数据包,并且不同的请求的数据包是混杂在一起的,也就是说一个数据包里面可能只是一个HTTP请求的一部分也可能包含多条HTTP请求的一部分,所以Parse()里面需要处理跨数据包的状态信息,我们要把这些数据包和具体的七层的应用层的消息关联起来。

    现在我们仔细看看Parse()接口的各个参数定义是做什么用的

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. Parse(pkt *Packet, tcptuple *common.TcpTuple,  
    2.         dir uint8, private ProtocolData) ProtocolData  


    pkt不用说了,是送进来的数据包,前面已经介绍了其数据结构,tcptuple是该数据包所属的TCP数据流所在的唯一标示(一个未关闭的TCP数据量包含若干数据包,直到TCP链接关闭),使用tcptuple.Hashable()获取唯一值;dir参数标示数据包在TCP数据流中的流向,和第一个TCP数据包方向一致是TcpDirectionOriginal,否则是TcpDirectionReverse;private参数可用来在TCP流中存储状态信息,可在运行时转换成具体的强类型,任意修改和传递给下一个Parse方法,简单来说就是进行中间数据的共享。

    下面看段MySQL模块里面的例子

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. priv := mysqlPrivateData{}  
    2.         if private != nil {  
    3.                 var ok bool  
    4.                 priv, ok = private.(mysqlPrivateData)  
    5.                 if !ok {  
    6.                         priv = mysqlPrivateData{}  
    7.                 }  
    8.         }  
    9.    
    10.         [ ... ]  
    11.    
    12.         return priv  


    上面的代码就是将private强制转换成mysqlPrivateData结构,然后再使用。我们再继续看后续怎么处理这些包的一个逻辑例子

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. ok, complete := mysqlMessageParser(priv.Data[dir])  
    2.                 if !ok {  
    3.                         // drop this tcp stream. Will retry parsing with the next  
    4.                         // segment in it  
    5.                         priv.Data[dir] = nil  
    6.                         logp.Debug("mysql", "Ignore MySQL message. Drop tcp stream.")  
    7.                         return priv  
    8.                 }  
    9.    
    10.                 if complete {  
    11.                         mysql.messageComplete(tcptuple, dir, stream)  
    12.                 } else {  
    13.                         // wait for more data  
    14.                         break  
    15.                 }  


    mysqlMessageParser是一个解析mysql消息的方法,细节我们忽略,我们只需要关心它的返回,ok标示成功或者失败,true则继续处理,false表示数据包不能用,那就直接忽略;第二个参数complete表示判断这一个MySQL消息是否已经完整了,如果完整了,我们就可以扔出去了,否则继续等待剩下的消息内容。

    好的,我们看看SMTP协议怎么折腾吧,先看看一个邮件交互的流程图,来自 RFC5321 (https://tools.ietf.org/html/rfc5321):

    由上图可见,发送端和邮件服务器通过一系列命令来执行邮件的发送,下面看看一个具体的命令操作流程(来源: https://zh.wikipedia.org/wiki/简单邮件传输协议 )

    [html] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. S: 220 www.example.com ESMTP Postfix  
    2. C: HELO mydomain.com  
    3. S: 250 Hello mydomain.com  
    4. C: MAIL FROM:     
    5.      
    6.    
    7. S: 250 Ok  
    8. C: RCPT TO:      
    9.       
    10.     
    11. S: 250 Ok  
    12. C: DATA  
    13. S: 354 End data with   
    14.     
    15.         
    16. C: Subject: test message  
    17. C: From:""sender@mydomain.com>  
    18. C: To:""friend@example.com>  
    19. C:  
    20. C: Hello,  
    21. C: This is a test.  
    22. C: Goodbye.  
    23. C: .  
    24. S: 250 Ok: queued as 12345  
    25. C: quit  
    26. S: 221 Bye  


    上面的过程可以看到就几个命令就能将邮件发送出去,但是其实SMTP协议比较复杂,还包括身份认证、附件、多媒体编码等等,我们今天精简一下,我们目前只关心谁给谁发了邮件,发送内容先不管,这样相比完整的SMTP协议( RFC5321 ),我们只需要关注以下几个命令:

    MAIL:开始一份邮件 mail from: xxx@xx.com

    RCPT: 标识单个的邮件接收人;常在mail命令后面 可有多个rcpt to: xx@xx.com

    QUIT:结束SMTP会话,不一定发送了邮件,注意

    RESET:重置会话,当前传输被取消

    最终希望通过Packetbeat将这些数据解析并处理成我们想要的如下JSON数据,即大功告成:

    [html] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. {  
    2. "timestamp":"2016-1-15 12:00:00",  
    3. "from":"medcl@example.co",  
    4. "to":["lcdem@example.co"]  
    5. }  


    我们还需要一个测试数据,这里有一个下载各种协议测试数据包的地方,由wireshark站点提供: https://wiki.wireshark.org/SampleCaptures/

    Ctrl+F找到SMTP的 下载地址(https://wiki.wireshark.org/SampleCaptures?action=AttachFile&do=get&target=smtp.pcap)

    用wireshark打开我们刚刚下载的smtp.pcap文件,然后再输入过滤条件:tcp.port == 25,只看25端口的数据,如下图:

    上图可以看到25端口的跑的数据有很多,不过我们只关心我们需要的那几个命令就好了。

    打开/~/go/src/github.com/elastic/beats/packetbeat/protos/smtp/smtp.go定义smtpPrivateData,里面的Data是一个数组,分别是TCP两个方向的数据,SmtpMessage是解析出来的邮件信息

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. type smtpPrivateData struct{  
    2.     Data [2]*SmtpStream  
    3. }  
    4.    
    5. type SmtpStream struct {  
    6.     tcptuple *common.TcpTuple  
    7.    
    8.     data []byte  
    9.    
    10.     parseOffset int  
    11.     isClient    bool  
    12.     message *SmtpMessage  
    13. }  
    14.    
    15. type SmtpMessage struct {  
    16.     Ts   time.Time  
    17.     From string  
    18.     To []string  
    19. }  


    然后参照MySQL协议,定义相应的方法,最终如下:

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. package smtp  
    2.    
    3. import (  
    4.     "github.com/elastic/beats/libbeat/common"  
    5.     "github.com/elastic/beats/libbeat/logp"  
    6.     "github.com/elastic/beats/libbeat/publisher"  
    7.     "github.com/elastic/beats/packetbeat/config"  
    8.     "github.com/elastic/beats/packetbeat/protos"  
    9.     "github.com/elastic/beats/packetbeat/protos/tcp"  
    10.     "bytes"  
    11.     "time"  
    12.     "strings"  
    13. )  
    14.    
    15. type smtpPrivateData struct{  
    16.     Data [2]*SmtpStream  
    17. }  
    18.    
    19. type SmtpStream struct {  
    20.     tcptuple *common.TcpTuple  
    21.    
    22.     data []byte  
    23.    
    24.     parseOffset int  
    25.     isClient    bool  
    26.    
    27.     message *SmtpMessage  
    28. }  
    29.    
    30. type SmtpMessage struct {  
    31.     start int  
    32.     end   int  
    33.    
    34.     Ts   time.Time  
    35.     From string  
    36.     To []string  
    37.     IgnoreMessage bool  
    38. }  
    39.    
    40. type Smtp struct {  
    41.     SendRequest         bool  
    42.     SendResponse        bool  
    43.     transactionTimeout time.Duration  
    44.     Ports         []int  
    45.     results publisher.Client  
    46. }  
    47.    
    48. func (smtp *Smtp) initDefaults() {  
    49.     smtp.SendRequest = false  
    50.     smtp.SendResponse = false  
    51.     smtp.transactionTimeout = protos.DefaultTransactionExpiration  
    52. }  
    53.    
    54. func (smtp *Smtp) setFromConfig(config config.Smtp) error {  
    55.     smtp.Ports = config.Ports  
    56.     if config.SendRequest != nil {  
    57.         smtp.SendRequest = *config.SendRequest  
    58.     }  
    59.     if config.SendResponse != nil {  
    60.         smtp.SendResponse = *config.SendResponse  
    61.     }  
    62.    
    63.     if config.TransactionTimeout != nil && *config.TransactionTimeout > 0 {  
    64.         smtp.transactionTimeout = time.Duration(*config.TransactionTimeout) * time.Second  
    65.     }  
    66.    
    67.     return nil  
    68. }  
    69.    
    70. func (smtp *Smtp) GetPorts() []int {  
    71.     return smtp.Ports  
    72. }  
    73.    
    74. func (smtp *Smtp) Init(test_mode bool, results publisher.Client) error {  
    75.     smtp.initDefaults()  
    76.    
    77.     if !test_mode {  
    78.         err := smtp.setFromConfig(config.ConfigSingleton.Protocols.Smtp)  
    79.         if err != nil {  
    80.             return err  
    81.         }  
    82.     }  
    83.     smtp.results = results  
    84.    
    85.     return nil  
    86. }  
    87.    
    88. func readLine(data []byte, offset int) (bool, string, int) {  
    89.     q := bytes.Index(data[offset:], []byte(" "))  
    90.     if q == -1 {  
    91.         return false, "", 0  
    92.     }  
    93.     return true, string(data[offset : offset+q]), offset + q + 2  
    94. }  
    95.    
    96. func (smtp *Smtp) Parse(pkt *protos.Packet, tcptuple *common.TcpTuple, dir uint8, private protos.ProtocolData, ) protos.ProtocolData {  
    97.    
    98.     defer logp.Recover("ParseSmtp exception")  
    99.    
    100.     priv := smtpPrivateData{}  
    101.     if private != nil {  
    102.         var ok bool  
    103.         priv, ok = private.(smtpPrivateData)  
    104.         if !ok {  
    105.             priv = smtpPrivateData{}  
    106.         }  
    107.     }  
    108.    
    109.     if priv.Data[dir] == nil {  
    110.         priv.Data[dir] = &SmtpStream{  
    111.             tcptuple: tcptuple,  
    112.             data:     pkt.Payload,  
    113.             message:  &SmtpMessage{Ts: pkt.Ts},  
    114.         }  
    115.     } else {  
    116.         // concatenate bytes  
    117.         priv.Data[dir].data = append(priv.Data[dir].data, pkt.Payload...)  
    118.         if len(priv.Data[dir].data) > tcp.TCP_MAX_DATA_IN_STREAM {  
    119.             logp.Debug("smtp", "Stream data too large, dropping TCP stream")  
    120.             priv.Data[dir] = nil  
    121.             return priv  
    122.         }  
    123.     }  
    124.    
    125.     stream := priv.Data[dir]  
    126.     for len(stream.data) > 0 {  
    127.         if stream.message == nil {  
    128.             stream.message = &SmtpMessage{Ts: pkt.Ts}  
    129.         }  
    130.    
    131.         ok, complete := stmpMessageParser(priv.Data[dir])  
    132.         if !ok {  
    133.             // drop this tcp stream. Will retry parsing with the next  
    134.             // segment in it  
    135.             priv.Data[dir] = nil  
    136.             logp.Debug("smtp", "Ignore SMTP message. Drop tcp stream. Try parsing with the next segment")  
    137.             return priv  
    138.         }  
    139.    
    140.         if complete {  
    141.             smtp.messageComplete(tcptuple, dir, stream)  
    142.         } else {  
    143.             logp.Debug("smtp","still wait message...")  
    144.             // wait for more data  
    145.             break  
    146.         }  
    147.     }  
    148.    
    149.     return priv  
    150. }  
    151.    
    152. func (smtp *Smtp) ConnectionTimeout() time.Duration {  
    153.     return smtp.transactionTimeout  
    154. }  
    155.    
    156. func stmpMessageParser(s *SmtpStream) (bool, bool) {  
    157.    
    158.     var value string=""  
    159.    
    160.     for s.parseOffset < len(s.data) {  
    161.    
    162.    
    163.         logp.Info("smtp", "Parse message: %s", string(s.data[s.parseOffset]))  
    164.    
    165.    
    166.         if strings.HasPrefix(string(s.data[s.parseOffset]),"MAIL" ) {  
    167.    
    168.             logp.Debug("smtp", "Hit MAIL command: %s", string(s.data[s.parseOffset]))  
    169.    
    170.             found, line, off := readLine(s.data, s.parseOffset)  
    171.             if !found {  
    172.                 return true, false  
    173.             }  
    174.    
    175.             value = line[1:]  
    176.             logp.Debug("smtp", "value  %s", value)  
    177.    
    178.             s.parseOffset = off  
    179.         } else {  
    180.             logp.Debug("smtp", "Unexpected message starting with %s", s.data[s.parseOffset:])  
    181.             return false, false  
    182.         }  
    183.     }  
    184.    
    185.     return true, false  
    186. }  
    187.    
    188. func handleSmtp(stmp *Smtp, m *SmtpMessage, tcptuple *common.TcpTuple,  
    189. dir uint8, raw_msg []byte) {  
    190.     logp.Info("smtp","handle smtp message...")  
    191.    
    192.     //TODO  
    193.    
    194. }  
    195.    
    196. // Called when the parser has identified a full message.  
    197. func (smtp *Smtp) messageComplete(tcptuple *common.TcpTuple, dir uint8, stream *SmtpStream) {  
    198.    
    199.     logp.Info("smtp","message completed...")  
    200.    
    201.     // all ok, ship it  
    202.     msg := stream.data[stream.message.start:stream.message.end]  
    203.    
    204.     if !stream.message.IgnoreMessage {  
    205.         handleSmtp(smtp, stream.message, tcptuple, dir, msg)  
    206.     }  
    207.    
    208.     // and reset message  
    209.     stream.PrepareForNewMessage()  
    210. }  
    211.    
    212. func (stream *SmtpStream) PrepareForNewMessage() {  
    213.     logp.Info("smtp","prepare for new message...")  
    214.    
    215.     stream.data = stream.data[stream.parseOffset:]  
    216.     stream.parseOffset = 0  
    217.     stream.isClient = false  
    218.     stream.message = nil  
    219. }  
    220.    
    221.    
    222.    
    223. func (smtp *Smtp) GapInStream(tcptuple *common.TcpTuple, dir uint8,  
    224. nbytes int, private protos.ProtocolData) (priv protos.ProtocolData, drop bool) {  
    225.    
    226.     defer logp.Recover("GapInStream(smtp) exception")  
    227.    
    228.     if private == nil {  
    229.         return private, false  
    230.     }  
    231.    
    232.     return private, true  
    233. }  
    234.    
    235. func (smtp *Smtp) ReceivedFin(tcptuple *common.TcpTuple, dir uint8,  
    236. private protos.ProtocolData) protos.ProtocolData {  
    237.    
    238.     logp.Info("smtp","stream closed...")  
    239.    
    240.     // TODO: check if we have data pending and either drop it to free  
    241.     // memory or send it up the stack.  
    242.     return private  
    243. }  


    现在切换到命令行,编译一下

    [html] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. cd ~/go/src/github.com/elastic/beats/packetbeat  
    2. make  


    编译成功,一个滚烫的packetbeat可执行文件就躺在当前目录下了,运行一下先,参数-I 指定pcap文件(还记得前面下载的那个测试文件吧)

    [html] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. ./packetbeat -d "smtp" -c etc/packetbeat.yml -I ~/Downloads/smtp.pcap  -e -N  


    运行查看控制台输出结果:

    [html] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. ➜  packetbeat git:(smtpbeat) ✗ ./packetbeat -d "smtp" -c etc/packetbeat.yml -I ~/Downloads/smtp.pcap  -e -N   
    2. 2016/01/15 10:12:19.058535 publish.go:191: INFO Dry run mode. All output types except the file based one are disabled.  
    3. 2016/01/15 10:12:19.058570 geolite.go:24: INFO GeoIP disabled: No paths were set under output.geoip.paths  
    4. 2016/01/15 10:12:19.058592 publish.go:262: INFO Publisher name: medcls-MacBook.local  
    5. 2016/01/15 10:12:19.058724 beat.go:145: INFO Init Beat: packetbeat; Version: 1.0.0  
    6. 2016/01/15 10:12:19.059758 beat.go:171: INFO packetbeat sucessfully setup. Start running.  
    7. 2016/01/15 10:12:20.155335 smtp.go:163: DBG  Parse message: 2  
    8. 2016/01/15 10:12:20.155416 smtp.go:180: DBG  Unexpected message starting with 250-xc90.websitewelcome.com Hello GP [122.162.143.157]  
    9. 250-SIZE 52428800  
    10. 250-PIPELINING  
    11. 250-AUTH PLAIN LOGIN  
    12. 250-STARTTLS  
    13. 250 HELP  
    14. 2016/01/15 10:12:22.310974 smtp.go:163: DBG  Parse message: F  
    15. 2016/01/15 10:12:22.311025 smtp.go:180: DBG  Unexpected message starting with From: "Gurpartap Singh"     
    16.      
    17.    
    18. To:      
    19.       
    20.     
    21. Subject: SMTP  
    22. Date: Mon, 5 Oct 2009 11:36:07 +0530  
    23. Message-ID: <000301ca4581$ef9e57f0$cedb07d0$@in>  
    24. MIME-Version: 1.0  
    25. ...  


    成功了,邮件内容都在控制台输出了,但这还不是我们要的最终结果,我需要里面的关键信息,我们继续修改smtp.go这个文件。

  • 相关阅读:
    html标签嵌套规则
    关于setTimeout和Promise执行顺序问题
    vue基础
    new操作符具体干了什么
    ["1", "2", "3"].map(parseInt)
    线性表
    树的一些概念和性质
    A*与IDA*
    树上启发式合并
    启发式合并
  • 原文地址:https://www.cnblogs.com/beautiful-code/p/6625520.html
Copyright © 2020-2023  润新知