2013年09月03日

http://www.ibm.com/developerworks/cn/linux/l-tuntap/

简介

虚拟网卡Tun/tap驱动是一个开源项目,支持很多的类UNIX平台,OpenVPN和Vtun都是基于它实现隧道包封装。本文将介绍tun/tap驱动的使用并分析虚拟网卡tun/tap驱动程序在linux环境下的设计思路。

tun/tap驱动程序实现了虚拟网卡的功能,tun表示虚拟的是点对点设备,tap表示虚拟的是以太网设备,这两种设备针对网络包实施不同的封装。利用tun/tap驱动,可以将tcp/ip协议栈处理好的网络分包传给任何一个使用tun/tap驱动的进程,由进程重新处理后再发到物理链路中。开源项目openvpn( http://openvpn.sourceforge.net)和Vtun( http://vtun.sourceforge.net)都是利用tun/tap驱动实现的隧道封装。

回页首

使用tun/tap驱动

在linux 2.4内核版本及以后版本中,tun/tap驱动是作为系统默认预先编译进内核中的。在使用之前,确保已经装载了tun/tap模块并建立设备文件:

#modprobe tun
#mknod /dev/net/tun c 10 200

参数c表示是字符设备, 10和200分别是主设备号和次设备号。

这样,我们就可以在程序中使用该驱动了。

使用tun/tap设备的示例程序(摘自openvpn开源项目 http://openvpn.sourceforge.net,tun.c文件)

int open_tun (const char *dev, char *actual, int size)
{
  struct ifreq ifr;
  int fd;
  char *device = "/dev/net/tun";
  if ((fd = open (device, O_RDWR)) < 0) //创建描述符
    msg (M_ERR, "Cannot open TUN/TAP dev %s", device);
  memset (&ifr, 0, sizeof (ifr));
  ifr.ifr_flags = IFF_NO_PI;
  if (!strncmp (dev, "tun", 3)) {
      ifr.ifr_flags |= IFF_TUN;
   }
  else if (!strncmp (dev, "tap", 3)) {
      ifr.ifr_flags |= IFF_TAP;
    }
  else {
    msg (M_FATAL, "I don't recognize device %s as a TUN or TAP device",dev);
    }
  if (strlen (dev) > 3)		/* unit number specified? */
    strncpy (ifr.ifr_name, dev, IFNAMSIZ);
  if (ioctl (fd, TUNSETIFF, (void *) &ifr) < 0) //打开虚拟网卡
    msg (M_ERR, "Cannot ioctl TUNSETIFF %s", dev);
  set_nonblock (fd);
  msg (M_INFO, "TUN/TAP device %s opened", ifr.ifr_name);
  strncpynt (actual, ifr.ifr_name, size);
  return fd;
}

调用上述函数后,就可以在shell命令行下使用ifconfig 命令配置虚拟网卡了,通过生成的字符设备描述符,在程序中使用read和write函数就可以读取或者发送给虚拟的网卡数据了。

回页首

Tun/tap驱动程序工作原理

做为虚拟网卡驱动,Tun/tap驱动程序的数据接收和发送并不直接和真实网卡打交道,而是通过用户态来转交。在linux下,要实现核心态和用户态数据的交互,有多种方式:可以通用socket创建特殊套接字,利用套接字实现数据交互;通过proc文件系统创建文件来进行数据交互;还可以使用设备文件的方式,访问设备文件会调用设备驱动相应的例程,设备驱动本身就是核心态和用户态的一个接口,Tun/tap驱动就是利用设备文件实现用户态和核心态的数据交互。

从结构上来说,Tun/tap驱动并不单纯是实现网卡驱动,同时它还实现了字符设备驱动部分。以字符设备的方式连接用户态和核心态。下面是示意图:

Tun/tap驱动程序中包含两个部分,一部分是字符设备驱动,还有一部分是网卡驱动部分。利用网卡驱动部分接收来自TCP/IP协议栈的网络分包并发送或者反过来将接收到的网络分包传给协议栈处理,而字符驱动部分则将网络分包在内核与用户态之间传送,模拟物理链路的数据接收和发送。Tun/tap驱动很好的实现了两种驱动的结合。

下面是定义的tun/tap设备结构:

struct tun_struct {
	char 			name[8]; //设备名
	unsigned long 		flags; //区分tun和tap设备
	struct fasync_struct    *fasync; //文件异步通知结构
	wait_queue_head_t 	read_wait; //等待队列
	struct net_device	dev;  //linux 抽象网络设备结构
	struct sk_buff_head 	txq; //网络缓冲区队列
    struct net_device_stats	stats; //网卡状态信息结构
};

struct net_device结构是linux内核提供的统一网络设备结构,定义了系统统一的访问接口。

Tun/tap驱动中实现的网卡驱动的处理例程:

static int tun_net_open(struct net_device *dev);
static int tun_net_close(struct net_device *dev);
static int tun_net_xmit(struct sk_buff *skb, struct net_device *dev);//数据包发送例程
static void tun_net_mclist(struct net_device *dev);//设置多点传输的地址链表
static struct net_device_stats *tun_net_stats(struct net_device *dev);//当一个应用程序需要知道 网络接口的一些统计数据时,可调用该函数,如ifconfig、netstat等。
int tun_net_init(struct net_device *dev);//网络设备初始例程

字符设备部分:

在Linux中,字符设备和块设备统一以文件的方式访问,访问它们的接口是统一的,都是使用open()函数打开设备文件或普通文件,用read()和write()函数实现读写文件等等。Tun/tap驱动定义的字符设备的访问接口如下:

static struct file_operations tun_fops = {
owner: THIS_MODULE,
llseek: tun_chr_lseek,
read tun_chr_read,
write: tun_chr_write,
poll: tun_chr_poll,
ioctl: tun_chr_ioctl,
open: tun_chr_open,
release: tun_chr_close,
fasync: tun_chr_fasync
};

在内核中利用misc_register() 函数将该驱动注册为非标准字符设备驱动,提供字符设备具有的各种程序接口。代码摘自linux-2.4.20\linux-2.4.20\drivers\net\tun.c

static struct miscdevice tun_miscdev=
{
        TUN_MINOR,
        "net/tun",
        &tun_fops
};
int __init tun_init(void)
{
	…
	if (misc_register(&tun_miscdev)) {
		printk(KERN_ERR "tun: Can't register misc device %d\n", TUN_MINOR);
		return -EIO;
	}
	return 0;
}

当打开一个tun/tap设备时,open 函数将调用tun_chr_open()函数,其中将完成一些重要的初始化过程,包括设置网卡驱动部分的初始化函数以及网络缓冲区链表的初始化和等待队列的初始化。Tun/tap驱动中网卡的注册被嵌入了字符驱动的ioctl例程中,它是通过对字符设备文件描述符利用自定义的ioctl设置标志TUNSETIFF完成网卡的注册的。下面是函数调用关系示意图:

使用ioctl()函数操作字符设备文件描述符,将调用字符设备中tun_chr_ioctl 来设置已经open好的tun/tap设备,如果设置标志为TUNSETIFF,则调用tun_set_iff() 函数,此函数将完成很重要的一步操作,就是对网卡驱动进行注册register_netdev(&tun->dev),网卡驱动的各个处理例程的挂接在open操作时由tun_chr_open()函数初始化好了。

Tun/tap设备的工作过程:

Tun/tap设备提供的虚拟网卡驱动,从tcp/ip协议栈的角度而言,它与真实网卡驱动并没有区别。从驱动程序的角度来说,它与真实网卡的不同表现在tun/tap设备获取的数据不是来自物理链路,而是来自用户区,Tun/tap设备驱动通过字符设备文件来实现数据从用户区的获取。发送数据时tun/tap设备也不是发送到物理链路,而是通过字符设备发送至用户区,再由用户区程序通过其他渠道发送。

发送过程:

使用tun/tap网卡的程序经过协议栈把数据传送给驱动程序,驱动程序调用注册好的hard_start_xmit函数发送,hard_start_xmit函数又会调用tun_net_xmit函数,其中skb将会被加入skb链表,然后唤醒被阻塞的使用tun/tap设备字符驱动读数据的进程,接着tun/tap设备的字符驱动部分调用其tun_chr_read()过程读取skb链表,并将每一个读到的skb发往用户区,完成虚拟网卡的数据发送。

接收数据的过程:

当我们使用write()系统调用向tun/tap设备的字符设备文件写入数据时,tun_chr_write函数将被调用,它使用tun_get_user从用户区接受数据,其中将数据存入skb中,然后调用关键的函数netif_rx(skb) 将skb送给tcp/ip协议栈处理,完成虚拟网卡的数据接收。

回页首

小结

tun/tap驱动很巧妙的将字符驱动和网卡驱动糅合在一起,本文重点分析两种驱动之间的衔接,具体的驱动处理细节没有一一列出,请读者参考相关文档。

参考资料

2013年02月23日

1,查看apache进程:

ps aux | grep httpd | grep -v grep | wc -l

2,查看80端口的tcp连接:

netstat -tan | grep “ESTABLISHED” | grep “:80″ | wc -l

3,通过日志查看当天ip连接数,过滤重复:

cat access_log | grep “20/Oct/2008″ | awk ‘{print $2}’ | sort | uniq -c | sort -nr

4,当天ip连接数最高的ip都在干些什么(原来是蜘蛛):

cat access_log | grep “20/Oct/2008:00″ | grep “122.102.7.212″ | awk ‘{print $8}’ | sort | uniq -c | sort -nr | head -n 10

5,当天访问页面排前10的url:

cat access_log | grep “20/Oct/2008:00″ | awk ‘{print $8}’ | sort | uniq -c | sort -nr | head -n 10

6,用tcpdump嗅探80端口的访问看看谁最高

tcpdump -i eth0 -tnn dst port 80 -c 1000 | awk -F”.” ‘{print $1″.”$2″.”$3″.”$4}’ | sort | uniq -c | sort -nr

接着从日志里查看该ip在干嘛:

cat access_log | grep 122.102.7.212| awk '{print $1"\t"$8}' | sort | uniq -c | sort -nr | less

7,查看某一时间段的ip连接数:

grep "2006:0[7-8]" www20060723.log | awk '{print $2}' | sort | uniq -c| sort -nr | wc -l

==============================nginx
log_format main '[$time_local] $remote_addr $status $request_time $body_bytes_sent "$request" "$http_referer"';
access_log /data0/logs/access.log main;

格式如下:
[21/Mar/2011:11:52:15 +0800] 58.60.188.61 200 0.265 28 "POST /event/time HTTP/1.1" "http://host/loupan/207846/feature"

通过日志查看当天ip连接数,过滤重复
cat access.log | grep "20/Mar/2011" | awk '{print $3}' | sort | uniq -c | sort -nr
38 112.97.192.16
20 117.136.31.145
19 112.97.192.31
3 61.156.31.20
2 209.213.40.6
1 222.76.85.28

当天访问页面排前10的url:
cat access.log | grep "20/Mar/2011" | awk '{print $8}' | sort | uniq -c | sort -nr | head -n 10

找出访问次数最多的10个IP
awk '{print $3}' access.log |sort |uniq -c|sort -nr|head
10680 10.0.21.17
1702 10.0.20.167
823 10.0.20.51
504 10.0.20.255
215 58.60.188.61
192 183.17.161.216
38 112.97.192.16
20 117.136.31.145
19 112.97.192.31
6 113.106.88.10

找出某天访问次数最多的10个IP
cat /tmp/access.log | grep "20/Mar/2011" |awk '{print $3}'|sort |uniq -c|sort -nr|head
38 112.97.192.16
20 117.136.31.145
19 112.97.192.31
3 61.156.31.20
2 209.213.40.6
1 222.76.85.28

当天ip连接数最高的ip都在干些什么:
cat access.log | grep "10.0.21.17" | awk '{print $8}' | sort | uniq -c | sort -nr | head -n 10
224 /test/themes/default/img/logo_index.gif
224 /test/themes/default/img/bg_index_head.jpg
224 /test/themes/default/img/bg_index.gif
219 /test/vc.php
219 /
213 /misc/js/global.js
211 /misc/jsext/popup.ext.js
211 /misc/js/common.js
210 /sladmin/home
197 /misc/js/flib.js

找出访问次数最多的几个分钟
awk '{print $1}' access.log | grep "20/Mar/2011" |cut -c 14-18|sort|uniq -c|sort -nr|head
24 16:49
19 16:17
16 16:51
11 16:48
4 16:50
3 16:52
1 20:09
1 20:05
1 20:03
1 19:55

2013年02月14日

$sql = “SELECT * FROM pinglun WHERE id >= ((SELECT MAX(id) FROM pinglun)-(SELECT MIN(id) FROM pinglun)) * RAND() + (SELECT MIN(id) FROM pinglun) LIMIT 1″;

这样写效率比较高,,千万不要用order by rand()那种,,数据量大了,order一下效率很低

2013年01月08日

之前使用ssh账户和myentunnel作为上国际互联网的代理,但是这个代理本身是socks类型。而ie浏览器只支持http方式的代理,所以将127.0.0.1:7070设置到ie代理后是没有作用的。

而利用Privoxy这个工具,就可以将socks代理转成http代理。转换方法如下:

修改Privoxy的配置文件,找到listen-address这一行,将其修改为:“listen-address 127.0.0.1:6060 ”, 6060也就是你需要的http代理的端口;
再找到“forward-socks5”这一行,去掉前面的注释标记#,将这一行修改为“forward-socks5 / 127.0.0.1:7070 . ”, 7070也就是socks代理的端口;
重启Privoxy就可以了。
最后在ie代理中填入127.0.0.1:6060,就可以访问国际互联网了。此时,也可以很方便的让别人的机器使用你的6060端口来上国际互联网~

这样做是为什么呢?因为有些软件,比如windows live writer,只支持http类型的代理,因此Privoxy还是很给力的。对了,这篇日志就是用windows live writer写的。

2012年12月24日

2012年12月20日

2012年11月27日

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;

import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;

public class DesFileEncrypt {
private static DesFileEncrypt Instance = null;

public static DesFileEncrypt getInstance() {
if (Instance == null) {
Instance = new DesFileEncrypt();
}
return Instance;
}

private Key key;
private Cipher cipherDecrypt, cipherEncrypt;

private DesFileEncrypt() {
super();
getKey(“as static void”);
initCipher();
}

public boolean encrypt(String oldFile, String newFile) {
try {
InputStream is = new FileInputStream(oldFile);
OutputStream out = new FileOutputStream(newFile);

CipherInputStream cis = new CipherInputStream(is, cipherEncrypt);
byte[] buffer = new byte[1024];
int r;
while ((r = cis.read(buffer)) > 0) {
out.write(buffer, 0, r);
}
cis.close();
is.close();
out.close();
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

public boolean decrypt(String oldFile, String newFile) {
try {
InputStream is = new FileInputStream(oldFile);
CipherInputStream cis = new CipherInputStream(is, cipherDecrypt);
BufferedReader reader = new BufferedReader(new InputStreamReader(
cis));
String line = null;

OutputStream out = new FileOutputStream(newFile);

byte[] buffer = new byte[1024];
int r;
while ((r = cis.read(buffer)) > 0) {
out.write(buffer, 0, r);
}

while ((line = reader.readLine()) != null) {
System.out.println(line);
}
reader.close();
cis.close();
is.close();
out.close();
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

private void initCipher() {
try {
cipherEncrypt = Cipher.getInstance(“DES”);
cipherEncrypt.init(Cipher.ENCRYPT_MODE, this.key);
cipherDecrypt = Cipher.getInstance(“DES”);
cipherDecrypt.init(Cipher.DECRYPT_MODE, this.key);
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
}
}

private Key getKey(String keyRule) {
byte[] keyByte = keyRule.getBytes();
byte[] byteTemp = new byte[8];
for (int i = 0; i < byteTemp.length && i < keyByte.length; i++) {
byteTemp[i] = keyByte[i];
}
key = new SecretKeySpec(byteTemp, "DES");
return key;
}

public boolean encrypt(String oneFile) {
if (encrypt(oneFile, oneFile + "_encrypt")) {
new File(oneFile).delete();
new File(oneFile + "_encrypt").renameTo(new File(oneFile));
return true;
} else {
return false;
}
}

public boolean decrypt(String oneFile) {
if (decrypt(oneFile, oneFile + "_encrypt")) {
new File(oneFile).delete();
new File(oneFile + "_encrypt").renameTo(new File(oneFile));
return true;
} else {
return false;
}
}

public static void main(String[] args) {
try {
// DesFileEncrypt.getInstance().encrypt(
// "c:\\Users\\asd\\Desktop\\a45.gif");
DesFileEncrypt.getInstance().decrypt(
"c:\\Users\\asd\\Desktop\\a45.gif");
} catch (Exception e) {
e.printStackTrace();
}
}
}

2012年11月25日

网上没太好的现成的代码,大部分写的太烂,自己改了一个。

public static boolean readInnerZipFile(String oriZipFile,
String innerZipFileEntryName, String outZipFileEntryName) {
ZipFile innerZipFile = null;
try {
innerZipFile = new ZipFile(oriZipFile);
Enumeration entries = innerZipFile.entries();
ZipEntry entryIn = null;
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
// System.out.println(entry);
if (entry.getName().compareToIgnoreCase(innerZipFileEntryName) == 0) {
entryIn = entry;
break;
}
}

if (entryIn != null) {
InputStream In = innerZipFile.getInputStream(entryIn);
OutputStream ow = new FileOutputStream(outZipFileEntryName,
true);
byte[] b = new byte[256];
int len = In.read(b);
while (len > 0) {
ow.write(b, 0, len);
len = In.read(b);
}
ow.flush();
ow.close();
In.close();
return true;
} else {
return false;
}
} catch (IOException e) {
e.printStackTrace();
return false;
}
}

2012年11月23日

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.ArrayList;

import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JTextField;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;

public class AutoCompleteTextfiled extends JTextField {
static final int MAX_NUM = 50;
/**
*
*/
private static final long serialVersionUID = 1L;

public AutoCompleteTextfiled(ArrayList items) {
super();
setupAutoComplete(this, items);
}

private static boolean isAdjusting(JComboBox cbInput) {
if (cbInput.getClientProperty("is_adjusting") instanceof Boolean) {
return (Boolean) cbInput.getClientProperty("is_adjusting");
}
return false;
}

private static void setAdjusting(JComboBox cbInput,
boolean adjusting) {
cbInput.putClientProperty("is_adjusting", adjusting);
}

@SuppressWarnings({ "rawtypes", "unchecked" })
public static void setupAutoComplete(final JTextField txtInput,
final ArrayList items) {
final DefaultComboBoxModel model = new DefaultComboBoxModel();

final JComboBox cbInput = new JComboBox(model) {
/**
*
*/
private static final long serialVersionUID = 1L;

public Dimension getPreferredSize() {
return new Dimension(super.getPreferredSize().width - 4, -36);
}
};
setAdjusting(cbInput, false);
for (String item : items) {
model.addElement(item);
}
cbInput.setSelectedItem(null);
cbInput.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (!isAdjusting(cbInput)) {
if (cbInput.getSelectedItem() != null) {
txtInput.setText(cbInput.getSelectedItem().toString());
}
}
}
});

txtInput.addKeyListener(new KeyAdapter() {

@Override
public void keyPressed(KeyEvent e) {
setAdjusting(cbInput, true);
// if (e.getKeyCode() == KeyEvent.VK_SPACE) {
// if (cbInput.isPopupVisible()) {
// e.setKeyCode(KeyEvent.VK_ENTER);
// }
// }
if (e.getKeyCode() == KeyEvent.VK_ENTER
|| e.getKeyCode() == KeyEvent.VK_UP
|| e.getKeyCode() == KeyEvent.VK_DOWN) {
e.setSource(cbInput);
cbInput.dispatchEvent(e);
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
if (cbInput.getSelectedItem() != null) {
txtInput.setText(cbInput.getSelectedItem()
.toString());
cbInput.setPopupVisible(false);
}
}
}
if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
cbInput.setPopupVisible(false);
}
setAdjusting(cbInput, false);
}
});
txtInput.getDocument().addDocumentListener(new DocumentListener() {
public void insertUpdate(DocumentEvent e) {
updateList();
}

public void removeUpdate(DocumentEvent e) {
updateList();
}

public void changedUpdate(DocumentEvent e) {
updateList();
}

private void updateList() {
setAdjusting(cbInput, true);
model.removeAllElements();
String input = txtInput.getText();
if (!input.isEmpty()) {
int i = 0;
for (String item : items) {
if (i 0);
cbInput.setPopupVisible(false);
cbInput.setPopupVisible(true);
setAdjusting(cbInput, false);
}
});
txtInput.setLayout(new BorderLayout());
txtInput.add(cbInput, BorderLayout.SOUTH);
}

/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub

}

}

2012年11月20日

import java.io.BufferedReader;
import java.io.File;
import java.io.FileWriter;
import java.io.InputStreamReader;

public class t1 {

/**
* @param args
*/
public static void main(String[] args) {
System.out.println(getMotherboardSN());
System.out.println(getSerialNumber(“C:”));
}

public static String getMotherboardSN() {
String result = “”;
try {
File file = File.createTempFile(“realhowto”, “.vbs”);
file.deleteOnExit();
FileWriter fw = new java.io.FileWriter(file);
String vbs = “Set objWMIService = GetObject(\”winmgmts:\\\\.\\root\\cimv2\”)\n”
+ “Set colItems = objWMIService.ExecQuery _ \n”
+ ” (\”Select * from Win32_BaseBoard\”) \n”
+ “For Each objItem in colItems \n”
+ ” Wscript.Echo objItem.SerialNumber \n”
+ ” exit for ‘ do the first cpu only! \n” + “Next \n”;
fw.write(vbs);
fw.close();
Process p = Runtime.getRuntime().exec(
“cscript //NoLogo ” + file.getPath());
BufferedReader input = new BufferedReader(new InputStreamReader(
p.getInputStream()));
String line;
while ((line = input.readLine()) != null) {
result += line;
}
input.close();
} catch (Exception e) {
e.printStackTrace();
}
return result.trim();
}

public static String getSerialNumber(String drive) {
String result = “”;
try {
File file = File.createTempFile(“realhowto”, “.vbs”);
file.deleteOnExit();
FileWriter fw = new java.io.FileWriter(file);
String vbs = “Set objFSO = CreateObject(\”Scripting.FileSystemObject\”)\n”
+ “Set colDrives = objFSO.Drives\n”
+ “Set objDrive = colDrives.item(\”"
+ drive
+ “\”)\n”
+ “Wscript.Echo objDrive.SerialNumber”; // see note
fw.write(vbs);
fw.close();
Process p = Runtime.getRuntime().exec(
“cscript //NoLogo ” + file.getPath());
BufferedReader input = new BufferedReader(new InputStreamReader(
p.getInputStream()));
String line;
while ((line = input.readLine()) != null) {
result += line;
}
input.close();
} catch (Exception e) {
e.printStackTrace();
}
return result.trim();
}
}