go ethereum crypto private public key

1. 生成Ethereum地址:

key, _ := crypto.GenerateKey()

address := crypto.PubkeyToAddress(key.PublicKey).Hex()

privateKey := hex.EncodeToString(key.D.Bytes())

2. 根据私钥16进制恢复地址

key,_ := crypto.HexToECDSA(testPrivHex)
msg := crypto.Keccak256([]byte("foo"))

sig, _ := crypto.Sign(msg, key)  #利用私钥对msg进行签名,同样可以通过签名的msg来恢复公钥,从而恢复地址

recoveredPub, _ := crypto.Ecrecover(msg, sig)
pubKey := crypto.ToECDSAPub(recoveredPub)
recoveredAddr := crypto.PubkeyToAddress(*pubKey)   #common.Address格式的地址
  • 2.1由此可见, 可以通过利用私钥签名,来推断出私钥对应的公钥,进而推断出地址。

  • 2.2 如下方式,也可以获取到公钥和地址

recoveredPub2,  _:= crypto.SigToPub(msg, sig)

recoveredAddr2 := crypto.PubkeyToAddress(*recoveredPub2)

Ecrecover 和 SigToPub都可以获得公钥!!

go ethereum create your own account

最近两天一直搜索哪里有开源的Go实现的Ethereum的钱包,但是发现其实那么多的开源项目并没有 符合我想看的,兜兜转转,还是看了go-ethereum的源码,然后在stackoverflow上看到了下面的 实现,值得一试。

stackoverflow

package main

import "github.com/ethereum/go-ethereum/crypto"
import "encoding/hex"
import "fmt"

func main() {
	
	//Create an account
	key, err := crypto.GenerateKey()
	if err != nil {
		fmt.Println("Error: ", err.Error());
	}
	
	//Get the address
	address := crypto.PubkeyToAddress(key.PublicKey).Hex()
	fmt.Printf("address[%d][%v]\n", len(address), address);
	
	//Get the private key
	privateKey := hex.EncodeToString(key.D.Bytes())
	fmt.Printf("privateKey[%d][%v]\n", len(privateKey), privateKey);
	
}

Golang iotuils devnull deserve deep thinking

ioutils源码地址

type devNull int


// devNull implements ReaderFrom as an optimization so io.Copy to

// ioutil.Discard can avoid doing unnecessary work.

var _ io.ReaderFrom = devNull(0)


func (devNull) Write(p []byte) (int, error) {

	return len(p), nil

}


func (devNull) WriteString(s string) (int, error) {

	return len(s), nil

}


var blackHolePool = sync.Pool{

	New: func() interface{} {

		b := make([]byte, 8192)

		return &b

	},

}


func (devNull) ReadFrom(r io.Reader) (n int64, err error) {

	bufp := blackHolePool.Get().(*[]byte)

	readSize := 0

	for {

		readSize, err = r.Read(*bufp)

		n += int64(readSize)

		if err != nil {

			blackHolePool.Put(bufp)

			if err == io.EOF {

				return n, nil

			}

			return

		}

	}

}


// Discard is an io.Writer on which all Write calls succeed

// without doing anything.

var Discard io.Writer = devNull(0)

使用样例,说到底这个devNull到底有什么用呢? 模拟一个黑洞,用来吃数据,而且黑洞是接口类型, 这个就是我能想到的仅有的解释了。

说实话,我自认为对于Go的语法已经懂得的不少了,但是看到了这些package的实现,还是觉得自己 太自已为是了。

var Discard io.Writer = devNull(0) //这个定义以及初始化我就没这个想象力!

var _ io.ReaderFrom = devNull(0)

//这个定义又如何理解呢? 没有定义变量,想说明什么? 让devNull必须实现ReadFrom方法? //这是我能想到的唯一目的了。

    package main  
      
    import (  
        "fmt"  
        "io"  
        "io/ioutil"  
        "strings"  
    )  
      
    func main() {  
        a := strings.NewReader("hello")  
        p := make([]byte, 20)  
        io.Copy(ioutil.Discard, a)  
        ioutil.Discard.Write(p)  
        fmt.Println(p)      //[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]  
    }  

netfilter xt_recent

Netfilter xt_recent module analyze

xt_recent模块用来记录最近的几次请求,并对其做特定规则处理,超过的则做另外的处理。

Netfilter模块有两个简单拓展模块, match, target. match - 指的是 -m xxxx, 例如-m recent, -m tcp 等。 target - 指的是 REDIRECT, ACCEPT, DROP 等。

这里分析的就是match模块。

下面来分析下这个内核模块的实现,学习下如何编写Netfilter内核模块, 注意看里面的注释。

/*
 * Copyright (c) 2006 Patrick McHardy <kaber@trash.net>
 * Copyright © CC Computer Consultants GmbH, 2007 - 2008
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * This is a replacement of the old ipt_recent module, which carried the
 * following copyright notice:
 *
 * Author: Stephen Frost <sfrost@snowman.net>
 * Copyright 2002-2003, Stephen Frost, 2.5.x port by laforge@netfilter.org
 */
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/init.h>
#include <linux/ip.h>
#include <linux/ipv6.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/string.h>
#include <linux/ctype.h>
#include <linux/list.h>
#include <linux/random.h>
#include <linux/jhash.h>
#include <linux/bitops.h>
#include <linux/skbuff.h>
#include <linux/inet.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <net/net_namespace.h>
#include <net/netns/generic.h>

#include <linux/netfilter/x_tables.h>
#include <linux/netfilter/xt_recent.h>

MODULE_AUTHOR("Patrick McHardy <kaber@trash.net>");
MODULE_AUTHOR("Jan Engelhardt <jengelh@medozas.de>");
MODULE_DESCRIPTION("Xtables: \"recently-seen\" host matching");
MODULE_LICENSE("GPL");
MODULE_ALIAS("ipt_recent");
MODULE_ALIAS("ip6t_recent");

static unsigned int ip_list_tot = 3000;
static unsigned int ip_pkt_list_tot = 255;
static unsigned int ip_list_hash_size = 0;
static unsigned int ip_list_perms = 0644;
static unsigned int ip_list_uid = 0;
static unsigned int ip_list_gid = 0;
module_param(ip_list_tot, uint, 0400);
module_param(ip_pkt_list_tot, uint, 0400);
module_param(ip_list_hash_size, uint, 0400);
module_param(ip_list_perms, uint, 0400);
module_param(ip_list_uid, uint, S_IRUGO | S_IWUSR);
module_param(ip_list_gid, uint, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(ip_list_tot, "number of IPs to remember per list");
MODULE_PARM_DESC(ip_pkt_list_tot, "number of packets per IP address to remember (max. 255)");
MODULE_PARM_DESC(ip_list_hash_size, "size of hash table used to look up IPs");
MODULE_PARM_DESC(ip_list_perms, "permissions on /proc/net/xt_recent/* files");
MODULE_PARM_DESC(ip_list_uid, "default owner of /proc/net/xt_recent/* files");
MODULE_PARM_DESC(ip_list_gid, "default owning group of /proc/net/xt_recent/* files");

struct recent_entry {
	struct list_head	list;
	struct list_head	lru_list;
	union nf_inet_addr	addr;
	u_int16_t		family;
	u_int8_t		ttl;
	u_int8_t		index;
	u_int16_t		nstamps;
	unsigned long		stamps[0];
};

struct recent_table {
	struct list_head	list;
	char			name[XT_RECENT_NAME_LEN];
	union nf_inet_addr	mask;
	unsigned int		refcnt;
	unsigned int		entries;
	struct list_head	lru_list;
	struct list_head	iphash[0];
};

struct recent_net {
	struct list_head	tables;
#ifdef CONFIG_PROC_FS
	struct proc_dir_entry	*xt_recent;
#endif
};

static int recent_net_id;
static inline struct recent_net *recent_pernet(struct net *net)
{
	return net_generic(net, recent_net_id);
}

static DEFINE_SPINLOCK(recent_lock);
static DEFINE_MUTEX(recent_mutex);

#ifdef CONFIG_PROC_FS
static const struct file_operations recent_old_fops, recent_mt_fops;
#endif

static u_int32_t hash_rnd __read_mostly;
static bool hash_rnd_inited __read_mostly;

static inline unsigned int recent_entry_hash4(const union nf_inet_addr *addr)
{
	return jhash_1word((__force u32)addr->ip, hash_rnd) &
	       (ip_list_hash_size - 1);
}

static inline unsigned int recent_entry_hash6(const union nf_inet_addr *addr)
{
	return jhash2((u32 *)addr->ip6, ARRAY_SIZE(addr->ip6), hash_rnd) &
	       (ip_list_hash_size - 1);
}

static struct recent_entry *
recent_entry_lookup(const struct recent_table *table,
		    const union nf_inet_addr *addrp, u_int16_t family,
		    u_int8_t ttl)
{
	struct recent_entry *e;
	unsigned int h;

	if (family == NFPROTO_IPV4)
		h = recent_entry_hash4(addrp);
	else
		h = recent_entry_hash6(addrp);

	list_for_each_entry(e, &table->iphash[h], list)
		if (e->family == family &&
		    memcmp(&e->addr, addrp, sizeof(e->addr)) == 0 &&
		    (ttl == e->ttl || ttl == 0 || e->ttl == 0))
			return e;
	return NULL;
}

static void recent_entry_remove(struct recent_table *t, struct recent_entry *e)
{
	list_del(&e->list);
	list_del(&e->lru_list);
	kfree(e);
	t->entries--;
}

/*
 * Drop entries with timestamps older then 'time'.
 */
static void recent_entry_reap(struct recent_table *t, unsigned long time)
{
	struct recent_entry *e;

	/*
	 * The head of the LRU list is always the oldest entry.
	 */
	e = list_entry(t->lru_list.next, struct recent_entry, lru_list);

	/*
	 * The last time stamp is the most recent.
	 */
	if (time_after(time, e->stamps[e->index-1]))
		recent_entry_remove(t, e);
}

static struct recent_entry *
recent_entry_init(struct recent_table *t, const union nf_inet_addr *addr,
		  u_int16_t family, u_int8_t ttl)
{
	struct recent_entry *e;

	if (t->entries >= ip_list_tot) {
		e = list_entry(t->lru_list.next, struct recent_entry, lru_list);
		recent_entry_remove(t, e);
	}
	e = kmalloc(sizeof(*e) + sizeof(e->stamps[0]) * ip_pkt_list_tot,
		    GFP_ATOMIC);
	if (e == NULL)
		return NULL;
	memcpy(&e->addr, addr, sizeof(e->addr));
	e->ttl       = ttl;
	e->stamps[0] = jiffies;
	e->nstamps   = 1;
	e->index     = 1;
	e->family    = family;
	//将entry加入table中,以addr计算出的hash值存储,提升查找效率
	if (family == NFPROTO_IPV4)
		list_add_tail(&e->list, &t->iphash[recent_entry_hash4(addr)]);
	else
		list_add_tail(&e->list, &t->iphash[recent_entry_hash6(addr)]);
	list_add_tail(&e->lru_list, &t->lru_list);
	t->entries++;
	return e;
}

static void recent_entry_update(struct recent_table *t, struct recent_entry *e)
{
	e->index %= ip_pkt_list_tot;
	e->stamps[e->index++] = jiffies;
	if (e->index > e->nstamps)
		e->nstamps = e->index;
	list_move_tail(&e->lru_list, &t->lru_list);
}

static struct recent_table *recent_table_lookup(struct recent_net *recent_net,
						const char *name)
{
	struct recent_table *t;

	list_for_each_entry(t, &recent_net->tables, list)
		if (!strcmp(t->name, name))
			return t;
	return NULL;
}

static void recent_table_flush(struct recent_table *t)
{
	struct recent_entry *e, *next;
	unsigned int i;

	for (i = 0; i < ip_list_hash_size; i++)
		list_for_each_entry_safe(e, next, &t->iphash[i], list)
			recent_entry_remove(t, e);
}

// Netfilter核心匹配函数
static bool
recent_mt(const struct sk_buff *skb, struct xt_action_param *par)
{
	struct net *net = dev_net(par->in ? par->in : par->out);
	struct recent_net *recent_net = recent_pernet(net);
	const struct xt_recent_mtinfo_v1 *info = par->matchinfo;
	struct recent_table *t;
	struct recent_entry *e;
	union nf_inet_addr addr = {}, addr_mask;
	u_int8_t ttl;
	bool ret = info->invert;

	if (par->family == NFPROTO_IPV4) {
		const struct iphdr *iph = ip_hdr(skb);

		if (info->side == XT_RECENT_DEST)
			addr.ip = iph->daddr;
		else
			addr.ip = iph->saddr;

		ttl = iph->ttl;
	} else {
		const struct ipv6hdr *iph = ipv6_hdr(skb);

		if (info->side == XT_RECENT_DEST)
			memcpy(&addr.in6, &iph->daddr, sizeof(addr.in6));
		else
			memcpy(&addr.in6, &iph->saddr, sizeof(addr.in6));

		ttl = iph->hop_limit;
	}

	/* use TTL as seen before forwarding */
	if (par->out != NULL && skb->sk == NULL)
		ttl++;

	//自旋锁
	spin_lock_bh(&recent_lock);
	//根据表名查找表,也就是通过--name指定的名字
	t = recent_table_lookup(recent_net, info->name);

	nf_inet_addr_mask(&addr, &addr_mask, &t->mask);

	//从表中根据地址掩码查找entry
	e = recent_entry_lookup(t, &addr_mask, par->family,
				(info->check_set & XT_RECENT_TTL) ? ttl : 0);
	
	//entry不存在,则初始化。
	if (e == NULL) {
		if (!(info->check_set & XT_RECENT_SET))
			goto out;
		e = recent_entry_init(t, &addr_mask, par->family, ttl);
		if (e == NULL)
			par->hotdrop = true;
		ret = !ret;
		goto out;
	}

	//匹配配置命令是SET, REMOVE, UPDATE, CHECK还是什么,并执行相应命令
	if (info->check_set & XT_RECENT_SET)
		ret = !ret;
	else if (info->check_set & XT_RECENT_REMOVE) {
		recent_entry_remove(t, e);
		ret = !ret;
	} else if (info->check_set & (XT_RECENT_CHECK | XT_RECENT_UPDATE)) {
		unsigned long time = jiffies - info->seconds * HZ;
		unsigned int i, hits = 0;

		for (i = 0; i < e->nstamps; i++) {
			//如果超过配置的seconds则继续,不超过就统计命中数,命中数超过就跳出循环
			if (info->seconds && time_after(time, e->stamps[i]))
				continue;
			if (!info->hit_count || ++hits >= info->hit_count) {
				ret = !ret;
				break;
			}
		}

		/* info->seconds must be non-zero */
		if (info->check_set & XT_RECENT_REAP)
			recent_entry_reap(t, time);
	}

	//更新表中的entry
	if (info->check_set & XT_RECENT_SET ||
	    (info->check_set & XT_RECENT_UPDATE && ret)) {
		recent_entry_update(t, e);
		e->ttl = ttl;
	}
out:
	//解自旋锁
	spin_unlock_bh(&recent_lock);
	return ret;
}

static void recent_table_free(void *addr)
{
	kvfree(addr);
}

// 在通过iptables命令配置xt_recent相关功能时检查参数是否合法
static int recent_mt_check(const struct xt_mtchk_param *par,
			   const struct xt_recent_mtinfo_v1 *info)
{
	struct recent_net *recent_net = recent_pernet(par->net);
	struct recent_table *t;
#ifdef CONFIG_PROC_FS
	struct proc_dir_entry *pde;
	kuid_t uid;
	kgid_t gid;
#endif
	unsigned int i;
	int ret = -EINVAL;
	size_t sz;

	if (unlikely(!hash_rnd_inited)) {
		get_random_bytes(&hash_rnd, sizeof(hash_rnd));
		hash_rnd_inited = true;
	}
	if (info->check_set & ~XT_RECENT_VALID_FLAGS) {
		pr_info("Unsupported user space flags (%08x)\n",
			info->check_set);
		return -EINVAL;
	}
	if (hweight8(info->check_set &
		     (XT_RECENT_SET | XT_RECENT_REMOVE |
		      XT_RECENT_CHECK | XT_RECENT_UPDATE)) != 1)
		return -EINVAL;
	if ((info->check_set & (XT_RECENT_SET | XT_RECENT_REMOVE)) &&
	    (info->seconds || info->hit_count ||
	    (info->check_set & XT_RECENT_MODIFIERS)))
		return -EINVAL;
	if ((info->check_set & XT_RECENT_REAP) && !info->seconds)
		return -EINVAL;
	if (info->hit_count > ip_pkt_list_tot) {
		pr_info("hitcount (%u) is larger than "
			"packets to be remembered (%u)\n",
			info->hit_count, ip_pkt_list_tot);
		return -EINVAL;
	}
	if (info->name[0] == '\0' ||
	    strnlen(info->name, XT_RECENT_NAME_LEN) == XT_RECENT_NAME_LEN)
		return -EINVAL;

	mutex_lock(&recent_mutex);
	t = recent_table_lookup(recent_net, info->name);
	if (t != NULL) {
		t->refcnt++;
		ret = 0;
		goto out;
	}

	sz = sizeof(*t) + sizeof(t->iphash[0]) * ip_list_hash_size;
	if (sz <= PAGE_SIZE)
		t = kzalloc(sz, GFP_KERNEL);
	else
		t = vzalloc(sz);
	if (t == NULL) {
		ret = -ENOMEM;
		goto out;
	}
	t->refcnt = 1;

	memcpy(&t->mask, &info->mask, sizeof(t->mask));
	strcpy(t->name, info->name);
	INIT_LIST_HEAD(&t->lru_list);
	for (i = 0; i < ip_list_hash_size; i++)
		INIT_LIST_HEAD(&t->iphash[i]);
#ifdef CONFIG_PROC_FS
	uid = make_kuid(&init_user_ns, ip_list_uid);
	gid = make_kgid(&init_user_ns, ip_list_gid);
	if (!uid_valid(uid) || !gid_valid(gid)) {
		recent_table_free(t);
		ret = -EINVAL;
		goto out;
	}
	pde = proc_create_data(t->name, ip_list_perms, recent_net->xt_recent,
		  &recent_mt_fops, t);
	if (pde == NULL) {
		recent_table_free(t);
		ret = -ENOMEM;
		goto out;
	}
	proc_set_user(pde, uid, gid);
#endif
	spin_lock_bh(&recent_lock);
	list_add_tail(&t->list, &recent_net->tables);
	spin_unlock_bh(&recent_lock);
	ret = 0;
out:
	mutex_unlock(&recent_mutex);
	return ret;
}

static int recent_mt_check_v0(const struct xt_mtchk_param *par)
{
	const struct xt_recent_mtinfo_v0 *info_v0 = par->matchinfo;
	struct xt_recent_mtinfo_v1 info_v1;

	/* Copy revision 0 structure to revision 1 */
	memcpy(&info_v1, info_v0, sizeof(struct xt_recent_mtinfo));
	/* Set default mask to ensure backward compatible behaviour */
	memset(info_v1.mask.all, 0xFF, sizeof(info_v1.mask.all));

	return recent_mt_check(par, &info_v1);
}

static int recent_mt_check_v1(const struct xt_mtchk_param *par)
{
	return recent_mt_check(par, par->matchinfo);
}

static void recent_mt_destroy(const struct xt_mtdtor_param *par)
{
	struct recent_net *recent_net = recent_pernet(par->net);
	const struct xt_recent_mtinfo_v1 *info = par->matchinfo;
	struct recent_table *t;

	mutex_lock(&recent_mutex);
	t = recent_table_lookup(recent_net, info->name);
	if (--t->refcnt == 0) {
		spin_lock_bh(&recent_lock);
		list_del(&t->list);
		spin_unlock_bh(&recent_lock);
#ifdef CONFIG_PROC_FS
		if (recent_net->xt_recent != NULL)
			remove_proc_entry(t->name, recent_net->xt_recent);
#endif
		recent_table_flush(t);
		recent_table_free(t);
	}
	mutex_unlock(&recent_mutex);
}

// 下面recent_seq_xxx 这些都是/proc/net/xt_recent/ 目录下的,用于动态生成文件系统
// 跟netfilter本身没有关系,忽略。
#ifdef CONFIG_PROC_FS
struct recent_iter_state {
	const struct recent_table *table;
	unsigned int		bucket;
};

static void *recent_seq_start(struct seq_file *seq, loff_t *pos)
	__acquires(recent_lock)
{
	struct recent_iter_state *st = seq->private;
	const struct recent_table *t = st->table;
	struct recent_entry *e;
	loff_t p = *pos;

	spin_lock_bh(&recent_lock);

	for (st->bucket = 0; st->bucket < ip_list_hash_size; st->bucket++)
		list_for_each_entry(e, &t->iphash[st->bucket], list)
			if (p-- == 0)
				return e;
	return NULL;
}

static void *recent_seq_next(struct seq_file *seq, void *v, loff_t *pos)
{
	struct recent_iter_state *st = seq->private;
	const struct recent_table *t = st->table;
	const struct recent_entry *e = v;
	const struct list_head *head = e->list.next;

	while (head == &t->iphash[st->bucket]) {
		if (++st->bucket >= ip_list_hash_size)
			return NULL;
		head = t->iphash[st->bucket].next;
	}
	(*pos)++;
	return list_entry(head, struct recent_entry, list);
}

static void recent_seq_stop(struct seq_file *s, void *v)
	__releases(recent_lock)
{
	spin_unlock_bh(&recent_lock);
}

static int recent_seq_show(struct seq_file *seq, void *v)
{
	const struct recent_entry *e = v;
	unsigned int i;

	i = (e->index - 1) % ip_pkt_list_tot;
	if (e->family == NFPROTO_IPV4)
		seq_printf(seq, "src=%pI4 ttl: %u last_seen: %lu oldest_pkt: %u",
			   &e->addr.ip, e->ttl, e->stamps[i], e->index);
	else
		seq_printf(seq, "src=%pI6 ttl: %u last_seen: %lu oldest_pkt: %u",
			   &e->addr.in6, e->ttl, e->stamps[i], e->index);
	for (i = 0; i < e->nstamps; i++)
		seq_printf(seq, "%s %lu", i ? "," : "", e->stamps[i]);
	seq_printf(seq, "\n");
	return 0;
}

static const struct seq_operations recent_seq_ops = {
	.start		= recent_seq_start,
	.next		= recent_seq_next,
	.stop		= recent_seq_stop,
	.show		= recent_seq_show,
};

static int recent_seq_open(struct inode *inode, struct file *file)
{
	struct recent_iter_state *st;

	st = __seq_open_private(file, &recent_seq_ops, sizeof(*st));
	if (st == NULL)
		return -ENOMEM;

	st->table    = PDE_DATA(inode);
	return 0;
}

static ssize_t
recent_mt_proc_write(struct file *file, const char __user *input,
		     size_t size, loff_t *loff)
{
	struct recent_table *t = PDE_DATA(file_inode(file));
	struct recent_entry *e;
	char buf[sizeof("+b335:1d35:1e55:dead:c0de:1715:5afe:c0de")];
	const char *c = buf;
	union nf_inet_addr addr = {};
	u_int16_t family;
	bool add, succ;

	if (size == 0)
		return 0;
	if (size > sizeof(buf))
		size = sizeof(buf);
	if (copy_from_user(buf, input, size) != 0)
		return -EFAULT;

	/* Strict protocol! */
	if (*loff != 0)
		return -ESPIPE;
	switch (*c) {
	case '/': /* flush table */
		spin_lock_bh(&recent_lock);
		recent_table_flush(t);
		spin_unlock_bh(&recent_lock);
		return size;
	case '-': /* remove address */
		add = false;
		break;
	case '+': /* add address */
		add = true;
		break;
	default:
		pr_info("Need \"+ip\", \"-ip\" or \"/\"\n");
		return -EINVAL;
	}

	++c;
	--size;
	if (strnchr(c, size, ':') != NULL) {
		family = NFPROTO_IPV6;
		succ   = in6_pton(c, size, (void *)&addr, '\n', NULL);
	} else {
		family = NFPROTO_IPV4;
		succ   = in4_pton(c, size, (void *)&addr, '\n', NULL);
	}

	if (!succ) {
		pr_info("illegal address written to procfs\n");
		return -EINVAL;
	}

	spin_lock_bh(&recent_lock);
	e = recent_entry_lookup(t, &addr, family, 0);
	if (e == NULL) {
		if (add)
			recent_entry_init(t, &addr, family, 0);
	} else {
		if (add)
			recent_entry_update(t, e);
		else
			recent_entry_remove(t, e);
	}
	spin_unlock_bh(&recent_lock);
	/* Note we removed one above */
	*loff += size + 1;
	return size + 1;
}

static const struct file_operations recent_mt_fops = {
	.open    = recent_seq_open,
	.read    = seq_read,
	.write   = recent_mt_proc_write,
	.release = seq_release_private,
	.owner   = THIS_MODULE,
	.llseek = seq_lseek,
};

static int __net_init recent_proc_net_init(struct net *net)
{
	struct recent_net *recent_net = recent_pernet(net);

	recent_net->xt_recent = proc_mkdir("xt_recent", net->proc_net);
	if (!recent_net->xt_recent)
		return -ENOMEM;
	return 0;
}

static void __net_exit recent_proc_net_exit(struct net *net)
{
	struct recent_net *recent_net = recent_pernet(net);
	struct recent_table *t;

	/* recent_net_exit() is called before recent_mt_destroy(). Make sure
	 * that the parent xt_recent proc entry is is empty before trying to
	 * remove it.
	 */
	spin_lock_bh(&recent_lock);
	list_for_each_entry(t, &recent_net->tables, list)
	        remove_proc_entry(t->name, recent_net->xt_recent);

	recent_net->xt_recent = NULL;
	spin_unlock_bh(&recent_lock);

	remove_proc_entry("xt_recent", net->proc_net);
}
#else
static inline int recent_proc_net_init(struct net *net)
{
	return 0;
}

static inline void recent_proc_net_exit(struct net *net)
{
}
#endif /* CONFIG_PROC_FS */

static int __net_init recent_net_init(struct net *net)
{
	struct recent_net *recent_net = recent_pernet(net);
	
	//初始化tables链表
	INIT_LIST_HEAD(&recent_net->tables);
	return recent_proc_net_init(net);
}

static void __net_exit recent_net_exit(struct net *net)
{
	recent_proc_net_exit(net);
}

static struct pernet_operations recent_net_ops = {
	.init	= recent_net_init,
	.exit	= recent_net_exit,
	.id	= &recent_net_id,
	.size	= sizeof(struct recent_net),
};

//match模块的核心结构体
static struct xt_match recent_mt_reg[] __read_mostly = {
	{
		.name       = "recent",
		.revision   = 0,
		.family     = NFPROTO_IPV4,
		.match      = recent_mt, //核心匹配函数,返回bool类型
		.matchsize  = sizeof(struct xt_recent_mtinfo),
		.checkentry = recent_mt_check_v0, //检查配置是否合法
		.destroy    = recent_mt_destroy,
		.me         = THIS_MODULE,
	},
	{
		.name       = "recent",
		.revision   = 0,
		.family     = NFPROTO_IPV6,
		.match      = recent_mt,
		.matchsize  = sizeof(struct xt_recent_mtinfo),
		.checkentry = recent_mt_check_v0,
		.destroy    = recent_mt_destroy,
		.me         = THIS_MODULE,
	},
	{
		.name       = "recent",
		.revision   = 1,
		.family     = NFPROTO_IPV4,
		.match      = recent_mt,
		.matchsize  = sizeof(struct xt_recent_mtinfo_v1),
		.checkentry = recent_mt_check_v1,
		.destroy    = recent_mt_destroy,
		.me         = THIS_MODULE,
	},
	{
		.name       = "recent",
		.revision   = 1,
		.family     = NFPROTO_IPV6,
		.match      = recent_mt,
		.matchsize  = sizeof(struct xt_recent_mtinfo_v1),
		.checkentry = recent_mt_check_v1,
		.destroy    = recent_mt_destroy,
		.me         = THIS_MODULE,
	}
};

static int __init recent_mt_init(void)
{
	int err;

	if (!ip_list_tot || !ip_pkt_list_tot || ip_pkt_list_tot > 255)
		return -EINVAL;
	ip_list_hash_size = 1 << fls(ip_list_tot);

	err = register_pernet_subsys(&recent_net_ops);
	if (err)
		return err;
	
	//Netfilter的匹配模块注册函数
	err = xt_register_matches(recent_mt_reg, ARRAY_SIZE(recent_mt_reg));
	if (err)
		unregister_pernet_subsys(&recent_net_ops);
	return err;
}

static void __exit recent_mt_exit(void)
{
	xt_unregister_matches(recent_mt_reg, ARRAY_SIZE(recent_mt_reg));
	unregister_pernet_subsys(&recent_net_ops);
}

module_init(recent_mt_init);
module_exit(recent_mt_exit);

存储结构为:

Table(Hash Table)
    |---------- entry (ip:192.168.4.12)
    |-------- entry (ip:192.168.4.33)
    |------  
    |--------
    |---------

简单理解,就是使用hash表存储每个IP地址经过的数据包数量, 每个IP代表一个entry, 也是一个链表。 所以这个结构就: 是通过hash查找特定的IP,然后遍历列表计算超时数据包和命中数是否满足,并执行相应 的target.

用hash表的原因在于,IP的数量很多的时候可以直接找到IP,而不需要通过遍历整个链表,提升查找效率。

使用recent模块的样例: 目的60s内单个ip匹配20个数据包连接,超过的接受,不超过的重定向到3128端口。

	iptables -t nat -N SQUID
	iptables -t nat -I SQUID -p tcp --dport 80 --syn -m recent --name webpool --rcheck --seconds 60 --hitcount 20 -j ACCEPT
	iptables -t nat -I SQUID -p tcp --dport 80 --syn -m recent --name webpool --set -j REDIRECT --to-ports 3128

Linux getsockopt sample usage

Linux getsockopt sample usage

	#include <linux/netfilter_ipv4.h> //SO_ORIGINAL_DST
	//Get the target ip and port address
	 int status;
	 socklen_t destlen;
	 struct sockaddr_in destaddr;
	 char destinationName[256];
	 int port;
	 memset(destinationName, 0, 256);
	 
	 status = getsockopt(afd, IPPROTO_IP, SO_ORIGINAL_DST, (struct sockaddr *) &destaddr, &destlen);
	 inet_ntop(AF_INET, (void *)&destaddr.sin_addr, destinationName, sizeof(destinationName));
	 port = ntohs(destaddr.sin_port);
	 
这里的SO_ORIGINAL_DST是用于netfilter REDIRECT target重定向过的数据包中的。

Golang gorilla context doc

翻译 github.com/gorilla/context/doc.go

包context存储的值在一个请求的声明周期中是共享的。

注意:gorilla/context, 比context.Context诞生的早,不能很好的支持http.Request.WithContext (引入于Go1.7). 你应该只使用gorilla/context或者换到新的 http.Request.Context().

例如, 一个路由可以设置从URL中提取出来的变量, 并且程序的处理者可以访问这些值, 或者这个可以用来存储会话值在请求的最后。还有集中其他的常用方法。

这个想法是Brad Fitzpatrick在go-nuts的邮件列表中提出的:

    http://groups.google.com/group/golang-nuts/msg/e2d679d303aa5d53

这里是基础用法: 首先定义你需要的键。 键的类型是interface{} 所以你可以存储任何类型。

这里我们定义一个键使用常用的int类型来必买名称冲突:

package foo

import (
    "github.com/gorilla/context"
)

type key int

const MyKey key = 0

然后设置一个变量。 变量是绑定到http.Request对象的, 所以你需要请求的实例来设置值:

context.Set(r, MyKey, "bar")

程序可以后续使用你提供的键来访问变量:

func MyHandler(w http.ResponseWriter, r *http.Request){
    // val is "bar".
    val := context.Get(r, foo.MyKey)

    // returns ("bar", true)
    val, ok := context.GetOk(r, foo.MyKey)
    //...
}

这是最基础的使用方式。 我们在下面讨论一些别的用法。

所有类型都可以存储在context中。强制使用一种类型, 把键变得私用,并且包裹Get() 和Set()来接受和返回特定类型的值:

type key int

const mykey key = 0

// GetMyKey returns a value for this package from the request values.
func GetMyKey(r *http.Request) SomeType{
    if rv := context.Get(r, mykey); rv != nil {
        return rv.(SomeType)
    }
    return nil
}

// SetMyKey sets a value for this package in the request values.
func SetMyKey(r *http.Request, val SomeType){
    context.Set(r, mykey, val)
}

变量必须在请求的结尾清除,以删除所有存储的值。这个可以在http.Handler中做, 在请求被处理的时候。 只需要调用Clear()

context.Clear(r)

…或者使用 ClearHandler(), 这个方便的包裹来一个http.Handler在请求声明周期的末尾来请求变量

包gorilla/mux 和gorilla/pat中的路由调用Clear()所以如果你使用他们中的一个,你不需要手动清除上下文。

hugo deploy as submodule on github

本Blog就是基于Hugo的,由于担心这次的操作记录丢失,我特意写了这篇博客来记录如何使用这个东西。

首先根据文档 gohugo中的第二部分

以上文章介绍了如何部署的大概情况,按照步骤来就不会有问题的。

但是维护起来需要熟悉git submodule的概念与使用,我自以为用过git submodule就安心的使用起来了,可是始终不得其意。 最终还是被我搞定了。

首先每次下载 https://github.com/qianguozheng/blog-hugo.git 的时候,总是没有自动更新子模块

	git submodule update --init --recursive

来自

然后是执行./deploy.sh的时候没有提交子模块。

这个问题是我偶然发现问题的,我当时并不在master分支,而是特定的分支,导致无法提交。 所以 checkout下,提交就好了

整个工作流程如下:

	git clone https://github.com/qianguozheng/blog-hugo.git
	cd blog-hugo;
	#下载子模块
	git submodule update --init --recursive
	
	cd public
	git checkout master
	cd ../
	
	#创建博客
	hugo new post/Golang-test.md
	
	#编辑博客等操作
	...
	
	#提交qianguozheng.github.io.git仓库
	./deploy.sh
	
	#提交blog-hugo仓库
	git add -A
	git commit -m "Update blog"
	git push origin master	

完美结束

结论

不懂就多做测试,总会解决的

Golang gorilla mux

翻译 github.com/gorilla/mux/doc.go

包mux实现了路由请求与分发。

mux代表“HTTP请求多路复用”。 像标准的http.ServeMux, mux.Router匹配进来的请求和一些注册的路径, 并且调用处理函数来处理匹配URL或其他条件的。 主要特色如下:

  • 请求可以基于URL主机, 路径,路径前缀, schemes, 头,请求值, HTTP方法或者特定的匹配方式。
  • URL主机可路径可以通过可选的正则表达式来匹配。
  • Registered URLs can be built, or “reversed”, which helps maintaining references to resources.
  • 路由可以作为子路由: 子路由仅仅当父路由匹配的时候才会比对。 这种方式可以定义遗嘱路径拥有共同的条件特征,如主机,路径牵住,或者其他的重复属性。 额外的,这个优化请求匹配。
  • 实现了http.Handler接口所以这个和标准的http.ServeMux兼容

我们来注册一些URL路径和处理函数:

func main(){
    r := mux.NewRouter()
    r.HandleFunc("/", HomeHandler)
    r.HandleFunc("/products", ProductsHandler)
    r.HandleFunc("/articles", ArticlesHandler)
    http.Handle("/", r)
}

我们注册了3个路径映射URL给处理函数。这个与http.HandleFunc()类似: 如果一个请求的URL符合路径,相应的处理函数被调用, 以(http.ResponseWriter, *http.Request)作为参数。

路径可以有变量。 他们定义使用这种格式 {name} 或者 {name:pattern}. 如果一个正常的表达模式未被定义, 匹配的变量将会是任何值,直到下一个斜线。例如:

r := mux.NewRouter()
r.HandleFunc("/products/{key}", ProductHandler)
r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler)
r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)

组可以在模式内使用,只要没有捕捉(?:re). 例如:

r.HandleFunc("/articles/{category}/{sort:(?:asc|desc|new)}", ArticleCategoryHandler)

名字可以用来创建一组路由变量的映射,可以通过调用mux.Vars()获得:

vars := mux.Vars(request)
category := vars["category"]

如果任何的capturing组存在, mux会panic()在解析的过程中。阻止这种情况发生,转换任何的capturing组为非capturing,例如 转换”/{sort:(asc|desc)}“为”/{sort:{?:asc|dest}}“. 这是从之前的不可预测的行为转换来的当capturing组存在的时候。

这就是所有你需要知道的基础。更高级的选项在下面解释。

路由同样可以限制一个域名或者子域名。 定义一个主机模式来匹配, 他们同样可以有变量:

r := mux.NewRouter()
//Only matches if domain is "www.example.com".
r.Host("www.example.com")
//Matches a dynamic subdomain.
r.Host("{subdomain:[a-z]+}.domain.com")

还有其他几种匹配可以添加,来匹配这种路径前缀:

r.PathPrefix("/products/")

…或者HTTP方法

r.Methods("GET", "POST")

…或者 URL schemes:

r.Schemes("https")

…或者 头部值

r.Headers("X-Requested-With", "XMLHttpRequest")

…或者 请求值

r.Queries("key", "value")

…或者 使用定制匹配函数

r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool{
    return r.ProtoMajor == 0
})

…最终, 也可以组合几种匹配到一个路由中:

r.HandleFunc("/products", ProductsHandler).
    Host("www.example.com").
    Methods("GET").
    Schemes("http")

一遍又一遍的设置同样的路径很烦人, 所以我们可以使用组来共享满足相同条件的路由:

r := mux.NewRouter()
s := r.Host("www.example.com").Subrouter()

然在在路由中注册子路由:

s.HandleFunc("/products/", ProductsHandler)
s.HandleFunc("/products/{key}", ProductHandler)
s.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)

以上三个URL路径只有当域名为 “www.example.com”时才会被测试, 因为子路有会首先被测试。这个不仅方便而且优化请求路径。你可以创建子路由结合任意树形的匹配器。

子路经可以用来创建域名或者路径“namespaces”: 你定义子路由在中间位置,然后其余部分可以基于子路由注册自己的路径

还有其他的子路由,当一个子路由有路径前缀时, 内部的路由使用它作为他们的基础:

r := mux.NewRouter()
s := r.PathPrefix("/products").Subrouter()
//"/products/"
s.HandleFunc("/", ProductHandler)
// "/products/{key}/"
s.HandleFunc("/{key}/", ProductHandler)
// "/products/{key}/details"
s.HandleFunc("/{key}/details", ProductDetailsHandler)

注意,路径提供给PathPrefix()代表一个“掩码”: 调用PathPrefix(“/static/”).Handler()意味着处理函数会传递任何请求符合”/static/*“.这种方式很容易实现静态文件的路由。

func main()
    var dir string
    flag.StringVar(&dir, "dir", ".", "the directory to serve files from. Defaults to the current dir")
    flag.Parse()
    r := mux.NewRouter()
    
    // This will serve files under http://localhost:8000/static/<filename>
    r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(dir))))

	srv := &http.Server{
		Handler:      r,
		Addr:         "127.0.0.1:8000",
		// Good practice: enforce timeouts for servers you create!
		WriteTimeout: 15 * time.Second,
		ReadTimeout:  15 * time.Second,
	}

	log.Fatal(srv.ListenAndServe())
}

现在我们来看看如何建立注册URL。

路由可以是名称, 所有定义名称的路由可以有自己的URLs, 或者反转。我们定义一个名称通过调用Name()在路由上。例如:

r := mux.NewRouter()
r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
Name("article")

建立URL,获取路由,调用URL()方法,传递一系列的键值对给路由变量。对于之前的路径,我们可以这样做:

url, err := r.Get("article").URL("category", "technology", "id", "42")

…并且结果会是url.URL带有下面的路径

"/articles/technology/42"

这个同样对于主机变量:

r := mux.NewRouter()
r.Host("{subdomain}.domain.com").
    Path("/articles/{category}/{id:[0-9]+}").
    HandlerFunc(ArticleHandler).
    Name("article")

// url.String() will be "http://news.domain.com/articles/technology/42"
url, err := r.Get("article").URL("subdomain", "news",
                                 "category", "technology",
                                 "id", "42")

所有路由中的变量都是必须的,并且他们的值必须服务响应模式/这些需求保证生成的URL会一直符合注册路径 – 唯一例外的是那些显示定义 “build-only”路由永远都不会匹配。

正则支持也存在于匹配路由中的头。例如,我们可以作:

r.HeaderRegexp("Content-Type", "application/(text|json)")

…路由会匹配请求带有Content-Type为application/json以及application/text的。

同样可以为路由建立只有URL主机或者路径的路由: 使用方法URLHost()或者URLPath()替代/ 对于之前的路由,我们可以这样做:

// "http://news.domain.com/"
host, err := r.Get("article").URLHost("subdomain", "news")

// "/articles/technology/42"
path, err := r.Get("article").URLPath("category", "technology", "id", "42")

如果你使用子路由, 主机和路径单独定义可以建立如下:

r := mux.NewRouter()
s := r.Host("{subdomain}.domain.com").Subrouter()
s.Path("/articles/{category}/{id:[0-9]+}").
  HandlerFunc(ArticleHandler).
  Name("article")

// "http://news.domain.com/articles/technology/42"
url, err := r.Get("article").URL("subdomain", "news",
                                 "category", "technology",
                                 "id", "42")

Golang session problem

Golang Session using problem

以前从事嵌入式工作,没有做过多用户的处理,比如web服务器上后台管理人员与普通用户的区别。

最近自己在写一个认证服务器的简单管理功能,想优化下就增加了admin管理权限。

初步设计

通过通过mux来管理路由。然后在访问/admin/路径下的API时需要检测是否登陆。

实现中遇到的问题

  1. mux的具体实现不知道,需要进一步了解,今后的文章要分析透彻。
  2. google了很多的管理登陆的文章,发现都是需要在处理的API内部做判断登陆,这点我感觉繁琐了,增加一个中间件不就好了吗,但是怎么增加是一个问题。
  3. 另外一个就是session的使用中遇到的了,我明明增加了session,但是就是没有收到数据。

中间件验证登陆解决方案

通过mux建立路由,所有访问/admin/目录下的请求需要先验证是否登陆,即判断是否存在session。

只需要

    router := mux.NewRouter()
	adminRoutes := mux.NewRouter()

	router.HandleFunc("/", index)

	adminRoutes.HandleFunc("/admin/", adminIndex)
	...
	
	router.PathPrefix("/admin").Handler(negroni.New(
		NewCheckLogin(),
		negroni.Wrap(adminRoutes),
	))
	// /account/login
	router.HandleFunc("/account/login", login)
	http.ListenAndServe(":8080", router)

negroni.New(New)eckLogin(),negroni.Wrap(adminRoutes) 这句是进行验证的,具体原理还是需要去研究下,目前不是非常的明白。

Session的使用比较奇怪,也是需要后续去学习领悟的地方。

除go基本类型外,复杂对象结构存储必须先注册。

import(
        "encoding/gob"
        "github.com/gorilla/sessions"
    )

    type Person struct {
        FirstName   string
        LastName    string
        Email       string
        Age         int
    }

    type M map[string]interface{}

    func init() {

        gob.Register(&Person{})
        gob.Register(&M{})
    }

Golang introducing HTTP tracing

介绍

在Go 1.7我们引入了HTTP跟踪, 用来收集HTTP客户端请求生命周期中详细信息的工作。包在net/http/httptrace. 手机的信息可以用来调试延时问题,服务监控,写适配系统等。

HTTP 事件

httptrace包提供了许多钩子在HTTP生命周期中收集信息。这些事件包括:

  • Connection creation
  • Connection reuse
  • DNS lookups
  • Writing the request to the wire
  • Reading the response

跟踪事件

你可以通过放置一个*httptrace.ClientTrace 包含钩子函数在请求的context.Context中来开启http追踪。各种http.RoundTripper实现通过查找上下文的 *httptrace.ClientTrace和调用相关的钩子函数来汇报内部事件

最终作用于请求的上下文,用户应该将一个 *httptrace.ClientTrace防到请求的上下文在开始请求之前。

    req, _ := http.NewRequest("GET", "http://example.com", nil)
    trace := &httptrace.ClientTrace{
        DNSDone: func(dnsInfo httptrace.DNSDoneInfo) {
            fmt.Printf("DNS Info: %+v\n", dnsInfo)
        },
        GotConn: func(connInfo httptrace.GotConnInfo) {
            fmt.Printf("Got Conn: %+v\n", connInfo)
        },
    }
    req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace))
    if _, err := http.DefaultTransport.RoundTrip(req); err != nil {
        log.Fatal(err)
    }

在请求流程中,http.DefaultTransport会在事件发生时出发钩子。上面的程序会打印DNS信息当DNS查询完成时。同样的会打印连接信息当链接与请求主机建立连接时

使用http.Client跟踪

跟踪机制设计用来跟踪单个的http.Transport.RoundTrip的声明周期事件。然而,一个客户端可以发起多个round trips来完成HTTP请求。例如,URL重定向,注册的钩子被调用多次当客户端跟踪HTTP重定向,发起多个请求。用户负责识别类似的事件在http.CLient中。下面的程序识别通过httpRoundTripper包裹识别当前的请求。

package main

import (
    "fmt"
    "log"
    "net/http"
    "net/http/httptrace"
)

// transport is an http.RoundTripper that keeps track of the in-flight
// request and implements hooks to report HTTP tracing events.
type transport struct {
    current *http.Request
}

// RoundTrip wraps http.DefaultTransport.RoundTrip to keep track
// of the current request.
func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) {
    t.current = req
    return http.DefaultTransport.RoundTrip(req)
}

// GotConn prints whether the connection has been used previously
// for the current request.
func (t *transport) GotConn(info httptrace.GotConnInfo) {
    fmt.Printf("Connection reused for %v? %v\n", t.current.URL, info.Reused)
}

func main() {
    t := &transport{}

    req, _ := http.NewRequest("GET", "https://google.com", nil)
    trace := &httptrace.ClientTrace{
        GotConn: t.GotConn,
    }
    req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace))

    client := &http.Client{Transport: t}
    if _, err := client.Do(req); err != nil {
        log.Fatal(err)
    }
}

程序将会跟踪重定向google.com 到www.google.com并打印如下:

Connection reused for https://google.com? false
Connection reused for https://www.google.com/? false

传输在net/http包支持跟踪http/1和http/2

如果你是一个定制的http.RoundTripper实现作者,你可以通过检查请求上下文并在相应的事件发生时触发钩子函数

结论

GO的HTTP跟踪对于那些感兴趣调试HTTP请求延时和些网络外部负载调试工具时。 通过开启这个功能,我们希望可以看到HTTP调试,性能。可视化工具来自社区例如httpstat.

By Jaana Burcu Dogan

Golang recover usage

Golang panic recover usage

Brief Introduction

In golang, some abnormal case would cause program crash, but if you want it recover, you need to handle it by hand.

Below is the simple example of http function use recover to recover from panic caused by peer-end server crash.

package main

import (
    "errors"
    "fmt"
    "net/http"
    "strings"
    "time"
)

var err error

func httppost() {
    for {
        fmt.Println("Cycle...")
        defer func() {

            if r := recover(); r != nil {

                fmt.Println("Recovered in testPanic2Error", r)

                //check exactly what the panic was and create error.
                switch x := r.(type) {
                case string:
                    err = errors.New(x)
                case error:
                    err = x
                default:
                    err = errors.New("Unknow panic")
                }
            }
            fmt.Println(err)
            httppost()

        }()
        resp, err := http.Post("http://127.0.0.1/v3/api/device/vpn",
            "application/x-www-form-urlencoded",
            strings.NewReader("name=qgz"))

        if err != nil {
            fmt.Println(err)
        }

        resp.Body.Close()

        time.Sleep(time.Second * 5)
        fmt.Println("Cycle...")
    }
}

func main() {
    httppost()
}

welcome

welcome to qianguozheng tech blog.