mojotv_cn

mojotv_cn 查看完整档案

北京编辑AUT University  |  CS 编辑360Qihoo  |  高级后端安全工程师 编辑 mojotv.cn 编辑
编辑

mojotv.cn
captch.mojotv.cn
github.com/libragen
github.com/mojocn

个人动态

mojotv_cn 发布了文章 · 8月15日

Go语言详解:HTTP断点续传多线程下载原理

赞 0 收藏 0 评论 0

mojotv_cn 发布了文章 · 7月30日

Golang单元测试和Interface(译)

赞 0 收藏 0 评论 0

mojotv_cn 发布了文章 · 7月29日

Golang从零开始构建简单的全文搜索引擎(译)

赞 0 收藏 0 评论 0

mojotv_cn 收藏了文章 · 7月19日

Go进阶45:Golang简洁的日志收集系统(替代ELKB)

golang_es_coverage.png

0. 原文地址: https://mojotv.cn/go/golang-ELKB

1. 背景

不废话,
Golang 日志查看疼点

  1. linux查看日志,一般开发者对linux命令不是很熟悉, 搜索日志更加难上加难
  2. JAVA生态 ELKB 日志收集搭建复杂,
  3. 需要的是一个快速查看搜索,客户端来搜索日志

1.1 前期准备

我们可以使用logrus hook 快速的输出日志到Elastic Search 在使用Chrome Elastic Search GUI插件快速的定位你想用的日志.

  1. 收集日志: https://github.com/sirupsen/l...
  2. 日志保存: docker elastic-search 数据库 : docker run -d --name es.dev -p 9201:9200 -p 9301:9300 -e "discovery.type=single-node" elastic/elasticsearch:6.7.1
  3. ES客户端: chrome插件 Chrome Plugin ElasticSearch Head

2. 安装ES数据库

你可以去网上搜搜索 es数据库安装,在本教程中我们就来安装一个 docker es 单节点 (处理golang日志性能足够)

  • 拉取镜像 docker pull elasticsearch:6.7.0
  • 安装docker docker run -d --name mojocn.es -p 9201:9200 -p 9301:9300 -e "discovery.type=single-node" elastic/elasticsearch:6.7.1

这样就您就启动了一个 名字为 mojocn.es 端口 9201的单节点数据库.

3. 编写实现 logrus HOOK interface (核心代码)

main.go 完整代码: https://github.com/mojocn/eslogrushook

package main

import (
    "context"
    "fmt"
    "github.com/olivere/elastic"
    "github.com/sirupsen/logrus"
    "log"
    "os"
    "strings"
    "time"
)

//cfg 配置文件
type cfg struct {
    LogLvl     string   // 日志级别
    EsAddrs    []string //ES addr
    EsUser     string   //ES user
    EsPassword string   //ES password
}

//setupLogrus 初始化logrus 同时把logrus的logger var 引用到这个common.Logger
func setupLogrus(cc cfg) error {
    //logFileName := fmt.Sprintf("%s_%s.log", os.Args[1], time.Now().Format("06_01_02T15_04_05"))
    //
    //f, err := os.Create(logFileName)
    //if err != nil {
    //    return err
    //}

    logLvl, err := logrus.ParseLevel(cc.LogLvl)
    if err != nil {
        return err
    }
    logrus.SetLevel(logLvl)
    //logrus.SetReportCaller(true)
    //logrus.SetFormatter(&logrus.JSONFormatter{})
    //使用console默认输出

    //logrus.SetOutput(f)

    logrus.SetReportCaller(true)
    //开启 logrus ES hook
    esh := newEsHook(cc)
    logrus.AddHook(esh)
    fmt.Printf(">= error 级别,查看日志 %#v  中的logrus* 索引\n", cc.EsAddrs)

    return nil
}
func main() {
    cc := cfg{
        LogLvl:     "error",
        EsAddrs:    []string{"http://es.felix.mojotv.cn:9202/"},
        EsUser:     "",
        EsPassword: "",
    }
    err := setupLogrus(cc)
    if err != nil {
        log.Fatal(err)
    }
    logrus.WithField("URI", "mojotv.cn").Error("I love my son Felix")
    //等待日志发送到ES
    time.Sleep(time.Second * 10)
}

//esHook 自定义的ES hook
type esHook struct {
    cmd    string // 记录启动的命令
    client *elastic.Client
}

//newEsHook 初始化
func newEsHook(cc cfg) *esHook {
    es, err := elastic.NewClient(
        elastic.SetURL(cc.EsAddrs...),
        elastic.SetBasicAuth(cc.EsUser, cc.EsPassword),
        elastic.SetSniff(false),
        elastic.SetHealthcheckInterval(15*time.Second),
        elastic.SetErrorLog(log.New(os.Stderr, "ES:", log.LstdFlags)),
        //elastic.SetInfoLog(log.New(os.Stdout, "ES:", log.LstdFlags)),
    )

    if err != nil {
        log.Fatal("failed to create Elastic V6 Client: ", err)
    }
    return &esHook{client: es, cmd: strings.Join(os.Args, " ")}
}

//Fire logrus hook interface 方法
func (hook *esHook) Fire(entry *logrus.Entry) error {
    doc := newEsLog(entry)
    doc["cmd"] = hook.cmd
    go hook.sendEs(doc)
    return nil
}

//Levels logrus hook interface 方法
func (hook *esHook) Levels() []logrus.Level {
    return []logrus.Level{
        logrus.PanicLevel,
        logrus.FatalLevel,
        logrus.ErrorLevel,
    }
}

//sendEs 异步发送日志到es
func (hook *esHook) sendEs(doc appLogDocModel) {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("send entry to es failed: ", r)
        }
    }()
    _, err := hook.client.Index().Index(doc.indexName()).Type("_doc").BodyJson(doc).Do(context.Background())
    if err != nil {
        log.Println(err)
    }

}

//appLogDocModel es model
type appLogDocModel map[string]interface{}

func newEsLog(e *logrus.Entry) appLogDocModel {
    ins := map[string]interface{}{}
    for kk, vv := range e.Data {
        ins[kk] = vv
    }
    ins["time"] = time.Now().Local()
    ins["lvl"] = e.Level
    ins["message"] = e.Message
    ins["caller"] = fmt.Sprintf("%s:%d  %#v", e.Caller.File, e.Caller.Line, e.Caller.Func)
    return ins
}

// indexName es index name 时间分割
func (m *appLogDocModel) indexName() string {
    return "mojocn-cn-" + time.Now().Local().Format("2006-01-02")
}

go run main.go 日志输出

>= error 级别,查看日志 []string{"http://i.love.mojotv.cn:9202/"}  中的logrus* 索引
time="2020-07-17T17:26:03+08:00" level=error msg=lld func=main.main file="D:/GolandProjects/logrusEsHook/main.go:59" URI=mojotv.cn

4. Chrome 插件 ElasticSearch Head 日志查看

  1. 配置ES 连接地址
  2. 查看/搜索 logrus日志

logrus_es_hook.png

查看原文

mojotv_cn 发布了文章 · 7月17日

Go进阶45:Golang简洁的日志收集系统(替代ELKB)

golang_es_coverage.png

0. 原文地址: https://mojotv.cn/go/golang-ELKB

1. 背景

不废话,
Golang 日志查看疼点

  1. linux查看日志,一般开发者对linux命令不是很熟悉, 搜索日志更加难上加难
  2. JAVA生态 ELKB 日志收集搭建复杂,
  3. 需要的是一个快速查看搜索,客户端来搜索日志

1.1 前期准备

我们可以使用logrus hook 快速的输出日志到Elastic Search 在使用Chrome Elastic Search GUI插件快速的定位你想用的日志.

  1. 收集日志: https://github.com/sirupsen/l...
  2. 日志保存: docker elastic-search 数据库 : docker run -d --name es.dev -p 9201:9200 -p 9301:9300 -e "discovery.type=single-node" elastic/elasticsearch:6.7.1
  3. ES客户端: chrome插件 Chrome Plugin ElasticSearch Head

2. 安装ES数据库

你可以去网上搜搜索 es数据库安装,在本教程中我们就来安装一个 docker es 单节点 (处理golang日志性能足够)

  • 拉取镜像 docker pull elasticsearch:6.7.0
  • 安装docker docker run -d --name mojocn.es -p 9201:9200 -p 9301:9300 -e "discovery.type=single-node" elastic/elasticsearch:6.7.1

这样就您就启动了一个 名字为 mojocn.es 端口 9201的单节点数据库.

3. 编写实现 logrus HOOK interface (核心代码)

main.go 完整代码: https://github.com/mojocn/eslogrushook

package main

import (
    "context"
    "fmt"
    "github.com/olivere/elastic"
    "github.com/sirupsen/logrus"
    "log"
    "os"
    "strings"
    "time"
)

//cfg 配置文件
type cfg struct {
    LogLvl     string   // 日志级别
    EsAddrs    []string //ES addr
    EsUser     string   //ES user
    EsPassword string   //ES password
}

//setupLogrus 初始化logrus 同时把logrus的logger var 引用到这个common.Logger
func setupLogrus(cc cfg) error {
    //logFileName := fmt.Sprintf("%s_%s.log", os.Args[1], time.Now().Format("06_01_02T15_04_05"))
    //
    //f, err := os.Create(logFileName)
    //if err != nil {
    //    return err
    //}

    logLvl, err := logrus.ParseLevel(cc.LogLvl)
    if err != nil {
        return err
    }
    logrus.SetLevel(logLvl)
    //logrus.SetReportCaller(true)
    //logrus.SetFormatter(&logrus.JSONFormatter{})
    //使用console默认输出

    //logrus.SetOutput(f)

    logrus.SetReportCaller(true)
    //开启 logrus ES hook
    esh := newEsHook(cc)
    logrus.AddHook(esh)
    fmt.Printf(">= error 级别,查看日志 %#v  中的logrus* 索引\n", cc.EsAddrs)

    return nil
}
func main() {
    cc := cfg{
        LogLvl:     "error",
        EsAddrs:    []string{"http://es.felix.mojotv.cn:9202/"},
        EsUser:     "",
        EsPassword: "",
    }
    err := setupLogrus(cc)
    if err != nil {
        log.Fatal(err)
    }
    logrus.WithField("URI", "mojotv.cn").Error("I love my son Felix")
    //等待日志发送到ES
    time.Sleep(time.Second * 10)
}

//esHook 自定义的ES hook
type esHook struct {
    cmd    string // 记录启动的命令
    client *elastic.Client
}

//newEsHook 初始化
func newEsHook(cc cfg) *esHook {
    es, err := elastic.NewClient(
        elastic.SetURL(cc.EsAddrs...),
        elastic.SetBasicAuth(cc.EsUser, cc.EsPassword),
        elastic.SetSniff(false),
        elastic.SetHealthcheckInterval(15*time.Second),
        elastic.SetErrorLog(log.New(os.Stderr, "ES:", log.LstdFlags)),
        //elastic.SetInfoLog(log.New(os.Stdout, "ES:", log.LstdFlags)),
    )

    if err != nil {
        log.Fatal("failed to create Elastic V6 Client: ", err)
    }
    return &esHook{client: es, cmd: strings.Join(os.Args, " ")}
}

//Fire logrus hook interface 方法
func (hook *esHook) Fire(entry *logrus.Entry) error {
    doc := newEsLog(entry)
    doc["cmd"] = hook.cmd
    go hook.sendEs(doc)
    return nil
}

//Levels logrus hook interface 方法
func (hook *esHook) Levels() []logrus.Level {
    return []logrus.Level{
        logrus.PanicLevel,
        logrus.FatalLevel,
        logrus.ErrorLevel,
    }
}

//sendEs 异步发送日志到es
func (hook *esHook) sendEs(doc appLogDocModel) {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("send entry to es failed: ", r)
        }
    }()
    _, err := hook.client.Index().Index(doc.indexName()).Type("_doc").BodyJson(doc).Do(context.Background())
    if err != nil {
        log.Println(err)
    }

}

//appLogDocModel es model
type appLogDocModel map[string]interface{}

func newEsLog(e *logrus.Entry) appLogDocModel {
    ins := map[string]interface{}{}
    for kk, vv := range e.Data {
        ins[kk] = vv
    }
    ins["time"] = time.Now().Local()
    ins["lvl"] = e.Level
    ins["message"] = e.Message
    ins["caller"] = fmt.Sprintf("%s:%d  %#v", e.Caller.File, e.Caller.Line, e.Caller.Func)
    return ins
}

// indexName es index name 时间分割
func (m *appLogDocModel) indexName() string {
    return "mojocn-cn-" + time.Now().Local().Format("2006-01-02")
}

go run main.go 日志输出

>= error 级别,查看日志 []string{"http://i.love.mojotv.cn:9202/"}  中的logrus* 索引
time="2020-07-17T17:26:03+08:00" level=error msg=lld func=main.main file="D:/GolandProjects/logrusEsHook/main.go:59" URI=mojotv.cn

4. Chrome 插件 ElasticSearch Head 日志查看

  1. 配置ES 连接地址
  2. 查看/搜索 logrus日志

logrus_es_hook.png

查看原文

赞 1 收藏 1 评论 0

mojotv_cn 收藏了文章 · 1月14日

golang:2FA双因素认证

image

原文地址https://mojotv.cn/go/golang-2fa

1. 前言

双重认证(英语:Two-factor authentication,缩写为2FA), 又译为双重验证、双因子认证、双因素认证、二元认证,又称两步骤验证(2-Step Verification,又译两步验证), 是一种认证方法,使用两种不同的元素,合并在一起,来确认用户的身份,是多因素验证中的一个特例.

  • 使用银行卡时,需要另外输入PIN码,确认之后才能使用其转账功能.
  • 登陆电脑版微信时,用已经登录同一账号的手机版微信扫描特定二维码进行验证.
  • 登陆校园网系统时,通过手机短信或学校指定的手机软件进行验证.
  • 登陆Steam和Uplay等游戏平台时,使用手机令牌或Google身份验证器进行验证.

2. TOTP的概念

TOTP 的全称是”基于时间的一次性密码”(Time-based One-time Password). 它是公认的可靠解决方案,已经写入国际标准 RFC6238.

它的步骤如下.

  • 第一步,用户开启双因素认证后,服务器生成一个密钥.
  • 第二步:服务器提示用户扫描二维码(或者使用其他方式),把密钥保存到用户的手机.也就是说,服务器和用户的手机,现在都有了同一把密钥.
  • 第三步,用户登录时,手机客户端使用这个密钥和当前时间戳,生成一个哈希,有效期默认为30秒.用户在有效期内,把这个哈希提交给服务器.(注意,密钥必须跟手机绑定.一旦用户更换手机,就必须生成全新的密钥.)
  • 第四步,服务器也使用密钥和当前时间戳,生成一个哈希,跟用户提交的哈希比对.只要两者不一致,就拒绝登录.

3.RFC6238

根据RFC 6238标准,供参考的实现如下:

  • 生成一个任意字节的字符串密钥K,与客户端安全地共享.
  • 基于T0的协商后,Unix时间从时间间隔(TI)开始计数时间步骤,TI则用于计算计数器C(默认情况下TI的数值是T0和30秒)的数值
  • 协商加密哈希算法(默认为SHA-1)
  • 协商密码长度(默认6位)

4. 2FA双因素认证 Golang 代码实现 TOTP

生成一次性密码的伪代码

function GoogleAuthenticatorCode(string secret)
  key := base32decode(secret)
  message := floor(current Unix time / 30)
  hash := HMAC-SHA1(key, message)
  offset := last nibble of hash
  truncatedHash := hash[offset..offset+3]  //4 bytes starting at the offset
  Set the first bit of truncatedHash to zero  //remove the most significant bit
  code := truncatedHash mod 1000000
  pad code with 0 until length of code is 6
  return code

生成事件性或计数性的一次性密码伪代码

function GoogleAuthenticatorCode(string secret)
  key := base32decode(secret)
  message := counter encoded on 8 bytes
  hash := HMAC-SHA1(key, message)
  offset := last nibble of hash
  truncatedHash := hash[offset..offset+3]  //4 bytes starting at the offset
  Set the first bit of truncatedHash to zero  //remove the most significant bit
  code := truncatedHash mod 1000000
  pad code with 0 until length of code is 6
  return code
  • 关于代码中为什么会出现难懂的位运算 -> 追求运算效率
package main

import (
    "crypto/hmac"
    "crypto/sha1"
    "encoding/binary"
    "fmt"
    "time"
)

func main() {
        key := []byte("MOJOTV_CN_IS_AWESOME_AND_AWESOME_SECRET_KEY")
        number := totp(key, time.Now(), 6)
        fmt.Println("2FA code: ",number)
}

func hotp(key []byte, counter uint64, digits int) int {
    //RFC 6238
    h := hmac.New(sha1.New, key)
    binary.Write(h, binary.BigEndian, counter)
    sum := h.Sum(nil)
    //取sha1的最后4byte
    //0x7FFFFFFF 是long int的最大值
    //math.MaxUint32 == 2^32-1
    //& 0x7FFFFFFF == 2^31  Set the first bit of truncatedHash to zero  //remove the most significant bit
    // len(sum)-1]&0x0F 最后 像登陆 (bytes.len-4)
    //取sha1 bytes的最后4byte 转换成 uint32
    v := binary.BigEndian.Uint32(sum[sum[len(sum)-1]&0x0F:]) & 0x7FFFFFFF
    d := uint32(1)
    
    //取十进制的余数
    for i := 0; i < digits && i < 8; i++ {
        d *= 10
    }
    return int(v % d)
}

func totp(key []byte, t time.Time, digits int) int {
    return hotp(key, uint64(t.Unix())/30, digits)
    //return hotp(key, uint64(t.UnixNano())/30e9, digits)
}

5. 参考

查看原文

mojotv_cn 发布了文章 · 1月14日

golang:2FA双因素认证

image

原文地址https://mojotv.cn/go/golang-2fa

1. 前言

双重认证(英语:Two-factor authentication,缩写为2FA), 又译为双重验证、双因子认证、双因素认证、二元认证,又称两步骤验证(2-Step Verification,又译两步验证), 是一种认证方法,使用两种不同的元素,合并在一起,来确认用户的身份,是多因素验证中的一个特例.

  • 使用银行卡时,需要另外输入PIN码,确认之后才能使用其转账功能.
  • 登陆电脑版微信时,用已经登录同一账号的手机版微信扫描特定二维码进行验证.
  • 登陆校园网系统时,通过手机短信或学校指定的手机软件进行验证.
  • 登陆Steam和Uplay等游戏平台时,使用手机令牌或Google身份验证器进行验证.

2. TOTP的概念

TOTP 的全称是”基于时间的一次性密码”(Time-based One-time Password). 它是公认的可靠解决方案,已经写入国际标准 RFC6238.

它的步骤如下.

  • 第一步,用户开启双因素认证后,服务器生成一个密钥.
  • 第二步:服务器提示用户扫描二维码(或者使用其他方式),把密钥保存到用户的手机.也就是说,服务器和用户的手机,现在都有了同一把密钥.
  • 第三步,用户登录时,手机客户端使用这个密钥和当前时间戳,生成一个哈希,有效期默认为30秒.用户在有效期内,把这个哈希提交给服务器.(注意,密钥必须跟手机绑定.一旦用户更换手机,就必须生成全新的密钥.)
  • 第四步,服务器也使用密钥和当前时间戳,生成一个哈希,跟用户提交的哈希比对.只要两者不一致,就拒绝登录.

3.RFC6238

根据RFC 6238标准,供参考的实现如下:

  • 生成一个任意字节的字符串密钥K,与客户端安全地共享.
  • 基于T0的协商后,Unix时间从时间间隔(TI)开始计数时间步骤,TI则用于计算计数器C(默认情况下TI的数值是T0和30秒)的数值
  • 协商加密哈希算法(默认为SHA-1)
  • 协商密码长度(默认6位)

4. 2FA双因素认证 Golang 代码实现 TOTP

生成一次性密码的伪代码

function GoogleAuthenticatorCode(string secret)
  key := base32decode(secret)
  message := floor(current Unix time / 30)
  hash := HMAC-SHA1(key, message)
  offset := last nibble of hash
  truncatedHash := hash[offset..offset+3]  //4 bytes starting at the offset
  Set the first bit of truncatedHash to zero  //remove the most significant bit
  code := truncatedHash mod 1000000
  pad code with 0 until length of code is 6
  return code

生成事件性或计数性的一次性密码伪代码

function GoogleAuthenticatorCode(string secret)
  key := base32decode(secret)
  message := counter encoded on 8 bytes
  hash := HMAC-SHA1(key, message)
  offset := last nibble of hash
  truncatedHash := hash[offset..offset+3]  //4 bytes starting at the offset
  Set the first bit of truncatedHash to zero  //remove the most significant bit
  code := truncatedHash mod 1000000
  pad code with 0 until length of code is 6
  return code
  • 关于代码中为什么会出现难懂的位运算 -> 追求运算效率
package main

import (
    "crypto/hmac"
    "crypto/sha1"
    "encoding/binary"
    "fmt"
    "time"
)

func main() {
        key := []byte("MOJOTV_CN_IS_AWESOME_AND_AWESOME_SECRET_KEY")
        number := totp(key, time.Now(), 6)
        fmt.Println("2FA code: ",number)
}

func hotp(key []byte, counter uint64, digits int) int {
    //RFC 6238
    h := hmac.New(sha1.New, key)
    binary.Write(h, binary.BigEndian, counter)
    sum := h.Sum(nil)
    //取sha1的最后4byte
    //0x7FFFFFFF 是long int的最大值
    //math.MaxUint32 == 2^32-1
    //& 0x7FFFFFFF == 2^31  Set the first bit of truncatedHash to zero  //remove the most significant bit
    // len(sum)-1]&0x0F 最后 像登陆 (bytes.len-4)
    //取sha1 bytes的最后4byte 转换成 uint32
    v := binary.BigEndian.Uint32(sum[sum[len(sum)-1]&0x0F:]) & 0x7FFFFFFF
    d := uint32(1)
    
    //取十进制的余数
    for i := 0; i < digits && i < 8; i++ {
        d *= 10
    }
    return int(v % d)
}

func totp(key []byte, t time.Time, digits int) int {
    return hotp(key, uint64(t.Unix())/30, digits)
    //return hotp(key, uint64(t.UnixNano())/30e9, digits)
}

5. 参考

查看原文

赞 1 收藏 1 评论 0

mojotv_cn 收藏了文章 · 1月14日

Golang-reflect反射的实际中的应用及畅想

1. 🎼 解决了什么

原文https://mojotv.cn/go/golang-reflect-string

我有很多行日志数据单行的格式是这样的

HOST;000012000629948340196501;ipv4;3; ips: user_id=2;user_name=172.21.1.102;policy_id=1;src_mac=52:54:00:62:7f:4a;dst_mac=58:69:6c:7b:fa:e7;src_ip=172.21.1.102;dst_ip=172.22.2.3;src_port=48612;dst_port=80;app_name=网页浏览(HTTP);protocol=TCP;app_protocol=HTTP;event_id=1310909;event_name=Microsoft_IIS_5.1_Frontpage扩展路径信息漏洞;event_type=安全漏洞;level=info;ctime=2019-12-26 11:17:17;action=pass

其中ips:之前的都是不规范的字段

我需要把他解析成结构化的数据,这样的

type IpsItem struct {
    UserId      int    `json:"user_id"`
    UserName    string `json:"user_name"`
    SrcIp       string `json:"src_ip"`
    DstIp       string `json:"dst_ip"`
    SrcPort     int    `json:"src_port"`
    DstPort     int    `json:"dst_port"`
    AppName     string `json:"app_name"`
    Protocol    string `json:"protocol"`
    AppProtocol string `json:"app_protocol"`
    EventId     int    `json:"event_id"`
    EventName   string `json:"event_name"`
    EventType   string `json:"event_type"`
    Level       string `json:"level"`
    Ctime       string `json:"ctime"`
    Action      string `json:"action"`
}

如果上面日志文件是json就非常容易解决了. 因为golang 标准库使用的就是 reflect反射生成struct.

所以我的思路也是使用reflect反射实现字符串转换成结构化的数据,你也可以大致了解标准库json.Unmarshal的原理.

2. 👀 直接上代码

package main

import (
    "fmt"
    "reflect"
    "strings"
)

var testRawString = "HOST;000012000629948340196501;ipv4;3; ips: user_id=2;user_name=172.21.1.102;policy_id=1;src_mac=52:54:00:62:7f:4a;dst_mac=58:69:6c:7b:fa:e7;src_ip=172.21.1.102;dst_ip=172.22.2.3;src_port=48612;dst_port=80;app_name=网页浏览(HTTP);protocol=TCP;app_protocol=HTTP;event_id=1311495;event_name=HTTP_Nikto_WEB漏洞扫描;event_type=安全扫描;level=warning;ctime=2019-12-26 11:17:17;action=pass"

type IpsItem struct {
    UserId      int    `json:"user_id"`
    UserName    string `json:"user_name"`
    SrcIp       string `json:"src_ip"`
    DstIp       string `json:"dst_ip"`
    SrcPort     int    `json:"src_port"`
    DstPort     int    `json:"dst_port"`
    AppName     string `json:"app_name"`
    Protocol    string `json:"protocol"`
    AppProtocol string `json:"app_protocol"`
    EventId     int    `json:"event_id"`
    EventName   string `json:"event_name"`
    EventType   string `json:"event_type"`
    Level       string `json:"level"`
    Ctime       string `json:"ctime"`
    Action      string `json:"action"`
}

func NewIpsItem(raw string) *IpsItem {
    //清除非法的字符
    raw = strings.ReplaceAll(raw, ":", ";")

    ins := IpsItem{}
    t := reflect.TypeOf(ins)
    //遍历结构体属性
    for i := 0; i < t.NumField(); i++ {
        //获取属性structField
        sf := t.Field(i)
        //属性名称
        fieldName := sf.Name
        //tag json的值
        tagName := sf.Tag.Get("json")

        //获取字段值
        fieldValue := reflect.ValueOf(&ins).Elem().FieldByName(fieldName)

        //属性的值 type
        switch sf.Type.Name() {
        case "int":
            var someInt int64
            scanValueFromString(raw, tagName, tagName+"=%d", &someInt)
            //给属性赋值
            fieldValue.SetInt(someInt)
            //todo:: 支持更多类型
        default:
            var someString string
            scanValueFromString(raw, tagName, tagName+"=%s", &someString)
            ////给属性赋值
            fieldValue.SetString(someString)
        }

    }
    return &ins
}

//scanValueFromString 字符串 字段的值
func scanValueFromString(raw string, tagJsonValue, format string, someV interface{}) {
    for _, ss := range strings.Split(raw, ";") {
        ele := strings.TrimSpace(ss)
        if strings.HasPrefix(ele, tagJsonValue) {
            fmt.Sscanf(ele, format, someV)
            //n, err := fmt.Sscanf(ele, format, someV)
            //fmt.Println(n, err)
            return
        }
    }
}

func main() {
    ii := NewIpsItem(testRawString)
    fmt.Printf("%+v\n", ii)
}

3. ☘ 抛砖颍玉

  • 使用反射开发gorm.model的自动文档工具
  • 开发自己的json/ini/yml/toml等格式的序列化库
  • 开发自己nginx 日志收集库
查看原文

mojotv_cn 发布了文章 · 1月14日

Golang-reflect反射的实际中的应用及畅想

1. 🎼 解决了什么

原文https://mojotv.cn/go/golang-reflect-string

我有很多行日志数据单行的格式是这样的

HOST;000012000629948340196501;ipv4;3; ips: user_id=2;user_name=172.21.1.102;policy_id=1;src_mac=52:54:00:62:7f:4a;dst_mac=58:69:6c:7b:fa:e7;src_ip=172.21.1.102;dst_ip=172.22.2.3;src_port=48612;dst_port=80;app_name=网页浏览(HTTP);protocol=TCP;app_protocol=HTTP;event_id=1310909;event_name=Microsoft_IIS_5.1_Frontpage扩展路径信息漏洞;event_type=安全漏洞;level=info;ctime=2019-12-26 11:17:17;action=pass

其中ips:之前的都是不规范的字段

我需要把他解析成结构化的数据,这样的

type IpsItem struct {
    UserId      int    `json:"user_id"`
    UserName    string `json:"user_name"`
    SrcIp       string `json:"src_ip"`
    DstIp       string `json:"dst_ip"`
    SrcPort     int    `json:"src_port"`
    DstPort     int    `json:"dst_port"`
    AppName     string `json:"app_name"`
    Protocol    string `json:"protocol"`
    AppProtocol string `json:"app_protocol"`
    EventId     int    `json:"event_id"`
    EventName   string `json:"event_name"`
    EventType   string `json:"event_type"`
    Level       string `json:"level"`
    Ctime       string `json:"ctime"`
    Action      string `json:"action"`
}

如果上面日志文件是json就非常容易解决了. 因为golang 标准库使用的就是 reflect反射生成struct.

所以我的思路也是使用reflect反射实现字符串转换成结构化的数据,你也可以大致了解标准库json.Unmarshal的原理.

2. 👀 直接上代码

package main

import (
    "fmt"
    "reflect"
    "strings"
)

var testRawString = "HOST;000012000629948340196501;ipv4;3; ips: user_id=2;user_name=172.21.1.102;policy_id=1;src_mac=52:54:00:62:7f:4a;dst_mac=58:69:6c:7b:fa:e7;src_ip=172.21.1.102;dst_ip=172.22.2.3;src_port=48612;dst_port=80;app_name=网页浏览(HTTP);protocol=TCP;app_protocol=HTTP;event_id=1311495;event_name=HTTP_Nikto_WEB漏洞扫描;event_type=安全扫描;level=warning;ctime=2019-12-26 11:17:17;action=pass"

type IpsItem struct {
    UserId      int    `json:"user_id"`
    UserName    string `json:"user_name"`
    SrcIp       string `json:"src_ip"`
    DstIp       string `json:"dst_ip"`
    SrcPort     int    `json:"src_port"`
    DstPort     int    `json:"dst_port"`
    AppName     string `json:"app_name"`
    Protocol    string `json:"protocol"`
    AppProtocol string `json:"app_protocol"`
    EventId     int    `json:"event_id"`
    EventName   string `json:"event_name"`
    EventType   string `json:"event_type"`
    Level       string `json:"level"`
    Ctime       string `json:"ctime"`
    Action      string `json:"action"`
}

func NewIpsItem(raw string) *IpsItem {
    //清除非法的字符
    raw = strings.ReplaceAll(raw, ":", ";")

    ins := IpsItem{}
    t := reflect.TypeOf(ins)
    //遍历结构体属性
    for i := 0; i < t.NumField(); i++ {
        //获取属性structField
        sf := t.Field(i)
        //属性名称
        fieldName := sf.Name
        //tag json的值
        tagName := sf.Tag.Get("json")

        //获取字段值
        fieldValue := reflect.ValueOf(&ins).Elem().FieldByName(fieldName)

        //属性的值 type
        switch sf.Type.Name() {
        case "int":
            var someInt int64
            scanValueFromString(raw, tagName, tagName+"=%d", &someInt)
            //给属性赋值
            fieldValue.SetInt(someInt)
            //todo:: 支持更多类型
        default:
            var someString string
            scanValueFromString(raw, tagName, tagName+"=%s", &someString)
            ////给属性赋值
            fieldValue.SetString(someString)
        }

    }
    return &ins
}

//scanValueFromString 字符串 字段的值
func scanValueFromString(raw string, tagJsonValue, format string, someV interface{}) {
    for _, ss := range strings.Split(raw, ";") {
        ele := strings.TrimSpace(ss)
        if strings.HasPrefix(ele, tagJsonValue) {
            fmt.Sscanf(ele, format, someV)
            //n, err := fmt.Sscanf(ele, format, someV)
            //fmt.Println(n, err)
            return
        }
    }
}

func main() {
    ii := NewIpsItem(testRawString)
    fmt.Printf("%+v\n", ii)
}

3. ☘ 抛砖颍玉

  • 使用反射开发gorm.model的自动文档工具
  • 开发自己的json/ini/yml/toml等格式的序列化库
  • 开发自己nginx 日志收集库
查看原文

赞 1 收藏 1 评论 0

mojotv_cn 收藏了文章 · 1月10日

Golang: math/rand 和 crypto/rand 区别

1. 前言

原文地址

之前发现了golang标准库中又两个rand软件包,开始非常想知道他们之间的差异.

math/rand软件包可以用于简单的游戏,但不能用于真正的随机性。

  • math/rand: 伪随机数生成器
  • crypto/rand: 加密安全的随机数生成器

Rob Pike的代码

package main

import (
    "fmt"
    "math/rand"
    "time"
)

func main() {
    c := fanIn(boring("Joe"), boring("Ann"))
    for i := 0; i < 10; i++ {
        fmt.Println(<-c)
    }
    fmt.Println("You're both boring; I'm leaving.")
}

func boring(msg string) <-chan string {
    c := make(chan string)
    go func() {
        for i := 0; ; i++ {
            c <- fmt.Sprintf("%s %d", msg, i)
            time.Sleep(time.Duration(rand.Intn(1e3)) * time.Millisecond)
        }
    }()
    return c
}

// FAN IN
func fanIn(input1, input2 <-chan string) <-chan string {
    c := make(chan string)
    go func() {
        for {
            c <- <-input1
        }
    }()
    go func() {
        for {
            c <- <-input2
        }
    }()
    return c
}

2. Math/rand 伪随机数生成器

实现伪随机数生成器。

随机数由源生成。顶级函数(例如Float64和Int)使用默认的共享源,该源在每次运行程序时都会产生确定的值序列。 如果每次运行需要不同的行为, 请使用种子函数初始化默认的源。 默认的Source可安全地供多个goroutine并发使用,但不是由NewSource创建的Source。

package main

import (
    "fmt"
    "math/rand"
    "time"
)
func init(){
    rand.Seed(time.Now().UTC().UnixNano())
}

func main() {

    // launches 2 generators and the fanIn collector function
    c := fanIn(genrt(), genrt())
    for i := 0; i < 10000; i++ {
        fmt.Println(<-c)
    }
}

func fanIn(a <-chan int, b <-chan int) <-chan string {
    c := make(chan string)
    // launch collector from a to channel
    go func() {
        var count int
        for {
            count += <-a
            c <- fmt.Sprintf("Tally of A is: %d", count)
        }
    }()
    // launch collector from b to channel
    go func() {
        var count int
        for {
            count += <-b
            c <- fmt.Sprintf("Tally of B is: %d", count)
        }
    }()

    return c
}

func genrt() <-chan int {
    c := make(chan int)
    // launch generator of Dice rolls
    go func() {
        for i := 0; ; i++ {
            c <- rand.Intn(6) + 1
            time.Sleep(time.Duration(500 * time.Millisecond))
        }
    }()
    return c
}


打印输出

...
Tally of B is: 17656
Tally of A is: 17438
Tally of A is: 17440
Tally of B is: 17659
Tally of B is: 17660
Tally of A is: 17445

3. Crypto/rand 加密安全的随机数生成器

实现了加密安全的随机数生成器。

package main
import (
    "crypto/rand"
    "fmt"
    "math/big"
    "time"
)

func main() {

    // launches 2 generatores and the fanIn collector function
    c := fanIn(genrt(), genrt())
    for i := 0; i < 10000; i++ {
        fmt.Println(<-c)
    }
}

func fanIn(a <-chan int, b <-chan int) <-chan string {
    c := make(chan string)
    // launch collector from a to channel
    go func() {
        var count int
        for {
            count += <-a
            c <- fmt.Sprintf("Tally of A is: %d", count)
        }
    }()
    // launch collector from b to channel
    go func() {
        var count int
        for {
            count += <-b
            c <- fmt.Sprintf("Tally of B is: %d", count)
        }
    }()

    return c
}

func genrt() <-chan int {
    c := make(chan int)
    // launch generator of Dice rolls
    go func() {
        for i := 0; ; i++ {
            dice, err := rand.Int(rand.Reader, big.NewInt(6))
            if err != nil {
                fmt.Println(err)
            }
            c <- int(dice.Int64()) + 1
            time.Sleep(time.Duration(1 * time.Millisecond))
        }
    }()
    return c
}

打印输出

...
Tally of B is: 17496
Tally of A is: 17570
Tally of A is: 17574
Tally of B is: 17500
Tally of B is: 17505
Tally of A is: 17576
查看原文

认证与成就

  • 获得 118 次点赞
  • 获得 4 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 4 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

注册于 2016-08-12
个人主页被 818 人浏览