Java實(shí)現(xiàn)抓包程序(網(wǎng)絡(luò)協(xié)議分析程序)

前言

本學(xué)期計(jì)算機(jī)網(wǎng)絡(luò)要求寫一個抓包程序,我通過網(wǎng)上查閱資料幕与,如何實(shí)現(xiàn)抓包挑势,實(shí)現(xiàn)了一個較為簡單的抓包程序。

項(xiàng)目準(zhǔn)備

1.首先得有java編譯環(huán)境啦鸣,安裝并配置好jdk潮饱;
2.需要安裝Winpcap,Winpcap是windows平臺下的一個免費(fèi)的诫给,公共的網(wǎng)絡(luò)訪問系統(tǒng)(Linux系統(tǒng)是Libpcap)香拉;
3.還需要下載Jpcap,Jpcap就是調(diào)用Winpcap給java提供一個公共的接口蝙搔,從而實(shí)現(xiàn)平臺無關(guān)性缕溉,并能夠捕獲發(fā)送數(shù)據(jù)包。Jpcap包括Jpcap.jar和Jpcap.dll吃型,兩者需要版本一致证鸥,并且區(qū)分32位和64位惧眠。將Jpcap.jar導(dǎo)入你的idea或者Eclipse項(xiàng)目腹忽,并且把Jpcap.dll復(fù)制到j(luò)ava的jdk的bin目錄下,就ok了届宠。

注:我的項(xiàng)目是用idea開發(fā)的赐写。

一鸟蜡、抓包功能的基本實(shí)現(xiàn)

前面的準(zhǔn)備工作完成后,我們就可以使用Jpcap編程進(jìn)行ip數(shù)據(jù)包的捕獲了挺邀。

  • JpcapHandler :這個接口用來定義分析被捕獲數(shù)據(jù)包的方法揉忘;

  • ARPPacket:這個類描述了ARP/RARP包,繼承了Packet類端铛;

  • DatalinkPacket :這個抽象類描述了數(shù)據(jù)鏈路層;

  • EthernetPacket :這個類描述了以太幀包,繼承DatalinkPacket類;

  • ICMPPacket:這個類描述了ICMP包,繼承了IPPacket類耻煤;

  • IPAddress:這個類描述了IPv4和IPv6地址,其中也包含了將IP地址轉(zhuǎn)換為域名的方法怜跑;

  • IPPacket:這個類描述了IP包,繼承了Packet類,支持IPv4和IPv6;

  • IPv6Option :這個類描述了IPv6選項(xiàng)報(bào)頭;

  • Jpcap:用來捕獲數(shù)據(jù)包;

  • Jpcap.JpcapInfo :Jpcap的內(nèi)部類,它包含被捕獲數(shù)據(jù)包的信息(在jpcap0.4修改部分BUG之后不再使用這個類);

  • JpcapSender :它用來發(fā)送一個數(shù)據(jù)包匪补;

  • JpcapWriter :它用來將一個被捕獲的數(shù)據(jù)包保存到文件;

  • Packet :這個類是所有被捕獲的數(shù)據(jù)包的基類竿滨;

  • TCPPacket:這個類描述TCP包,繼承了IPPacket類;

  • UDPPacket :這個類描述了UDP包前痘,繼承了IPPacket類;

    以抓取ip數(shù)據(jù)包為例,JPCAP抓包基本步驟為:綁定網(wǎng)絡(luò)設(shè)備、抓包、分析皮官。
    以下附上基本功能實(shí)現(xiàn)的代碼(無界面摄乒,能夠基本實(shí)現(xiàn)抓包功能):

import java.io.IOException;
 
import jpcap.*;
import jpcap.packet.IPPacket;
import jpcap.packet.Packet;
 
public class JpcapPacket {
    public static void main(String[] args)
    {
        /*--------------    第一步綁定網(wǎng)絡(luò)設(shè)備       --------------*/ 
        NetworkInterface[] devices = JpcapCaptor.getDeviceList();
        
        for(NetworkInterface n : devices)
        {
            System.out.println(n.name + "     |     " + n.description);
        }
        System.out.println("-------------------------------------------");
        
        JpcapCaptor jpcap = null;
        int caplen = 1512;
        boolean promiscCheck = true;
        
        /*
        devices[ ]中的數(shù)字需要注意梨水,這里的數(shù)字根據(jù)你的網(wǎng)卡而定,
        你選擇抓包的網(wǎng)卡正確才能抓到數(shù)據(jù)包,
        不同設(shè)備在使用有線網(wǎng)和無線網(wǎng)時的都不一樣,
        具體的需要自己去試驗(yàn)奕短。
        */
        try{
            jpcap = JpcapCaptor.openDevice(devices[1], caplen, promiscCheck, 50);
        }catch(IOException e)
        {
            e.printStackTrace();
        }
        
        /*----------第二步抓包-----------------*/
        int i = 0;
        while(i < 10)
        {
            Packet packet  = jpcap.getPacket();
            if(packet instanceof IPPacket && ((IPPacket)packet).version == 4)
            {
                i++;
                IPPacket ip = (IPPacket)packet;//強(qiáng)轉(zhuǎn)
                
                System.out.println("版本:IPv4");
                System.out.println("優(yōu)先權(quán):" + ip.priority);
                System.out.println("區(qū)分服務(wù):最大的吞吐量: " + ip.t_flag);
                System.out.println("區(qū)分服務(wù):最高的可靠性:" + ip.r_flag);
                System.out.println("長度:" + ip.length);
                System.out.println("標(biāo)識:" + ip.ident);
                System.out.println("DF:Don't Fragment: " + ip.dont_frag);
                System.out.println("NF:Nore Fragment: " + ip.more_frag);
                System.out.println("片偏移:" + ip.offset);
                System.out.println("生存時間:"+ ip.hop_limit);
                
                String protocol ="";
                switch(new Integer(ip.protocol))
                {
                case 1:protocol = "ICMP";break;
                case 2:protocol = "IGMP";break;
                case 6:protocol = "TCP";break;
                case 8:protocol = "EGP";break;
                case 9:protocol = "IGP";break;
                case 17:protocol = "UDP";break;
                case 41:protocol = "IPv6";break;
                case 89:protocol = "OSPF";break;
                default : break;
                }
                System.out.println("協(xié)議:" + protocol);
                System.out.println("源IP " + ip.src_ip.getHostAddress());
                System.out.println("目的IP " + ip.dst_ip.getHostAddress());
                System.out.println("源主機(jī)名: " + ip.src_ip);
                System.out.println("目的主機(jī)名: " + ip.dst_ip);
                System.out.println("----------------------------------------------");
            }
        }
        
    }
}

二宜肉、完整項(xiàng)目實(shí)現(xiàn)

具有界面,能夠?qū)崿F(xiàn)基本功能(查看網(wǎng)卡信息翎碑,開始抓包谬返,暫停抓包(開始抓包后“開始”按鈕變?yōu)椤皶和!比砧荆蹇战缑鎯?nèi)容遣铝,退出,以及過濾器功能的簡單實(shí)現(xiàn)))莉擒,界面如下圖:

界面如圖

注:界面使用swing實(shí)現(xiàn)(現(xiàn)在swing基本很少用了酿炸,不過做個簡單界面還是不錯)

1.界面布局

JpCapFrame代碼如下:

package packetCapture;

import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.JTableHeader;
import java.awt.*;

/**
 * 流式布局
 */
public class JpCapFrame extends JFrame {
    private static DefaultTableModel model;
    private static JTextField filterField;
    private JTextArea showArea;
    private JButton startBtn;
    private JButton checkBtn;
    private JButton exitBtn;
    private JButton clearBtn;

    public JpCapFrame() {
        super();
        initGUI();
    }

    public static DefaultTableModel getModel() {
        return model;
    }

    public JTextArea getShowArea() {
        return showArea;
    }

    public JButton getStartBtn() {
        return startBtn;
    }

    public JButton getCheckBtn() {
        return checkBtn;
    }

    public JButton getExitBtn() {
        return exitBtn;
    }

    public JButton getClearBtn() {
        return clearBtn;
    }

    public static JTextField getFilterField() {
        return filterField;
    }

    private void initGUI() {
        Font font1 = new Font("宋體", Font.BOLD, 15);
        Font font4 = new Font("宋體", Font.BOLD, 14);
        Font font2 = new Font("宋體", Font.PLAIN, 16);
        Font font3 = new Font("微軟雅黑", Font.PLAIN, 16);

        //界面
        setSize(1550, 1000);
        setVisible(true);
        setTitle("Captor");
        Container container = this.getContentPane();

        //頂部
        JPanel pane = new JPanel();
        pane.setBounds(0, 0, 775, 150);
        pane.setLayout(new FlowLayout(FlowLayout.LEFT, 10, 0));
        pane.setPreferredSize(new Dimension(775, 27));

        checkBtn = new JButton("查看網(wǎng)卡信息");
        checkBtn.setFont(font4);
        checkBtn.setBounds(0, 0, 50, 0);
        pane.add(checkBtn);

        startBtn = new JButton("開始");
        startBtn.setFont(font4);
        startBtn.setBounds(0, 0, 50, 0);
        pane.add(startBtn);

        clearBtn = new JButton("清空");
        clearBtn.setFont(font4);
        clearBtn.setBounds(0, 0, 50, 0);
        pane.add(clearBtn);

        exitBtn = new JButton("退出");
        exitBtn.setFont(font4);
        exitBtn.setBounds(0, 0, 50, 0);
        pane.add(exitBtn);

        JPanel panelTest = new JPanel();
        panelTest.setBounds(775, 0, 775, 150);
        panelTest.setPreferredSize(new Dimension(775, 27));
        panelTest.setLayout(new FlowLayout(FlowLayout.RIGHT, 20, 0));

        JLabel filter = new JLabel("Filter:");
        filter.setFont(font1);
        filter.setBounds(0, 0, 500, 0);
        filterField = new JTextField(50);
        filterField.setBounds(200, 0, 500, 0);
        panelTest.add(filter);
        panelTest.add(filterField);

        //中部主體內(nèi)容顯示區(qū)
        String[] name = {"No.", "Time", "Source", "Destination", "Protocol", "Length", "Info"};
        //model = new DefaultTableModel();
        //model.setColumnIdentifiers(name);
        JTable table = new JTable(model);
        JTableHeader tableHeader = table.getTableHeader();
        tableHeader.setFont(font1);
        table.setFont(font2);
        table.setRowHeight(20);
        model = (DefaultTableModel) table.getModel();
        model.setColumnIdentifiers(name);
        table.setEnabled(false);
        JScrollPane jScrollPane = new JScrollPane(table);
        jScrollPane.setBounds(0, 300, 1550, 600);

        //底部
        JPanel pane2 = new JPanel();
        pane2.setLayout(new BorderLayout());
        pane2.setPreferredSize(new Dimension(1550, 300));

        showArea = new JTextArea(5, 5);
        //showArea.setBounds(0,0,1200,300);
        //showArea.setText("Test");
        showArea.setEditable(false);
        showArea.setLineWrap(false);
        showArea.setFont(font3);
        //showArea.setBackground(Color.GRAY);
        //pane2.add(showArea);
        pane2.setSize(10, 10);
        pane2.setBounds(0, 0, 1, 1);
        //給textArea添加滾動條
        JScrollPane scrollPane = new JScrollPane(showArea);
        scrollPane.setBounds(0, 0, 1, 1);
        scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
        scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);

        pane2.add(scrollPane, BorderLayout.CENTER);
        scrollPane.setViewportView(showArea);

        container.add(jScrollPane, BorderLayout.CENTER);
        container.add(pane, BorderLayout.NORTH);
        container.add(panelTest, BorderLayout.NORTH);
        container.add(pane2, BorderLayout.SOUTH);

        setLocationRelativeTo(null);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
}

2.抓包功能管理類

JpCapPacket代碼如下:

package packetCapture;

import jpcap.JpcapCaptor;
import jpcap.packet.IPPacket;
import jpcap.packet.Packet;

import java.sql.Timestamp;
import java.util.Vector;

public class JpCapPacket {
    private JpcapCaptor jpcap;

    public JpCapPacket(JpcapCaptor jpcap) {
        this.jpcap = jpcap;
    }

    void capture() throws InterruptedException {
        int i = 0;
        while (true) {
            synchronized (JpCapMain.getThread()) {
                if (JpCapMain.isPause()) {
                    JpCapMain.getThread().wait();
                }
            }
            Packet packet = jpcap.getPacket();
            if (packet instanceof IPPacket && ((IPPacket) packet).version == 4) {
                i++;
                IPPacket ip = (IPPacket) packet;//強(qiáng)轉(zhuǎn)

//                System.out.println("版本:IPv4");
//                System.out.println("優(yōu)先權(quán):" + ip.priority);
//                System.out.println("區(qū)分服務(wù):最大的吞吐量: " + ip.t_flag);
//                System.out.println("區(qū)分服務(wù):最高的可靠性:" + ip.r_flag);
//                System.out.println("長度:" + ip.length);
//                System.out.println("標(biāo)識:" + ip.ident);
//                System.out.println("DF:Don't Fragment: " + ip.dont_frag);
//                System.out.println("NF:Nore Fragment: " + ip.more_frag);
//                System.out.println("片偏移:" + ip.offset);
//                System.out.println("生存時間:" + ip.hop_limit);

                String protocol = "";
                switch (new Integer(ip.protocol)) {
                    case 1:
                        protocol = "ICMP";
                        break;
                    case 2:
                        protocol = "IGMP";
                        break;
                    case 6:
                        protocol = "TCP";
                        break;
                    case 8:
                        protocol = "EGP";
                        break;
                    case 9:
                        protocol = "IGP";
                        break;
                    case 17:
                        protocol = "UDP";
                        break;
                    case 41:
                        protocol = "IPv6";
                        break;
                    case 89:
                        protocol = "OSPF";
                        break;
                    default:
                        break;
                }
//                System.out.println("協(xié)議:" + protocol);
//                System.out.println("源IP " + ip.src_ip.getHostAddress());
//                System.out.println("目的IP " + ip.dst_ip.getHostAddress());
//                System.out.println("源主機(jī)名: " + ip.src_ip);
//                System.out.println("目的主機(jī)名: " + ip.dst_ip);
//                System.out.println("----------------------------------------------");
                String filterInput = JpCapFrame.getFilterField().getText();
                if (filterInput.equals(ip.src_ip.getHostAddress()) ||
                        filterInput.equals(ip.dst_ip.getHostAddress()) ||
                        filterInput.equals(protocol) ||
                        filterInput.equals("")) {
                    Vector dataVector = new Vector();
                    Timestamp timestamp = new Timestamp((packet.sec * 1000) + (packet.usec / 1000));

                    dataVector.addElement(i + "");
                    //dataVector.addElement(new SimpleDateFormat("yyyy年MM月dd日HH時mm分ss").format(new Date()));
                    dataVector.addElement(timestamp.toString());//數(shù)據(jù)包時間
                    dataVector.addElement(ip.src_ip.getHostAddress());
                    dataVector.addElement(ip.dst_ip.getHostAddress());
                    dataVector.addElement(protocol);
                    dataVector.addElement(packet.data.length);

                    String strtmp = "";
                    for (int j = 0; j < packet.data.length; j++) {
                        strtmp += Byte.toString(packet.data[j]);
                    }
                    dataVector.addElement(strtmp); //數(shù)據(jù)內(nèi)容

                    JpCapFrame.getModel().addRow(dataVector);
                }
            }
        }
    }
}

3.主界面及功能實(shí)現(xiàn)

JpCapMain代碼如下:

package packetCapture;

import jpcap.JpcapCaptor;
import jpcap.NetworkInterface;

import java.io.IOException;

public class JpCapMain implements Runnable {
    JpCapFrame frame;
    JpcapCaptor jpcap = null;
    private static Thread thread = null;
    private static boolean pause = true;

    public JpCapMain() {
        //創(chuàng)建界面
        frame = new JpCapFrame();
        frame.setVisible(true);

        //綁定網(wǎng)絡(luò)設(shè)備
        NetworkInterface[] devices = JpcapCaptor.getDeviceList();

        int caplen = 1512;
        boolean promiscCheck = true;

        /*
        WIFI:3
        有線:1
        (不同設(shè)備對應(yīng)的不一樣)
        */
        int device = 1;
        try {
            jpcap = JpcapCaptor.openDevice(devices[device], caplen, promiscCheck, 50);
        } catch (IOException e) {
            e.printStackTrace();
        }

        frame.getCheckBtn().addActionListener(e -> {
            frame.getShowArea().append("當(dāng)前設(shè)備全部網(wǎng)絡(luò)設(shè)備信息為: \n");

            for (NetworkInterface n : devices) {
                System.out.println(n.name + "     |     " + n.description);
                frame.getShowArea().append(n.name + "     |     " + n.description + "\n");
            }
            //System.out.println("-------------------------------------------");
            frame.getShowArea().append(printSeparator(110, 0));
            frame.getShowArea().append("\n當(dāng)前使用網(wǎng)卡信息: " + devices[device].name + "     |     " + devices[device].description + "\n");
            frame.getShowArea().append(printSeparator(110, 1));
        });

        frame.getStartBtn().addActionListener(e -> {
            if (pause) {
                if (thread == null) {
                    frame.getShowArea().append("   開始抓包,抓取范圍為:" + JpCapFrame.getFilterField().getText() + " ……\n");
                    thread = new Thread(this);
                    thread.setPriority(Thread.MIN_PRIORITY);
                    //thread.sleep(100);
                    thread.start();
                    pause = false;
                    frame.getStartBtn().setText("暫停");
                } else {
                    frame.getStartBtn().setText("暫停");
                    pause = false;
                    frame.getShowArea().append("   繼續(xù)抓包,抓取范圍為:" + JpCapFrame.getFilterField().getText() + " ……\n");
                    synchronized (thread) {
                        thread.notify();
                    }
                }
            } else {
                pause = true;
                frame.getStartBtn().setText("開始");
                frame.getShowArea().append("    暫停抓包\n");
            }
        });

        frame.getClearBtn().addActionListener(e -> {
            frame.getShowArea().setText("");
            frame.getModel().setRowCount(0);
        });

        frame.getExitBtn().addActionListener(e -> {
            System.exit(0);
        });
    }

    public static void main(String[] args) {
        new JpCapMain();
    }

    @Override
    public void run() {
        try {
            new JpCapPacket(jpcap).capture();
            thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * @param separator "-"的數(shù)量
     * @param line      "\n"的數(shù)量
     * @return
     */
    public String printSeparator(int separator, int line) {
        String s = "";
        String l = "";

        for (int i = 0; i < separator; i++) {
            s += "-";
        }

        for (int i = 0; i < line; i++) {
            l += "\n";
        }
        return s + l;
    }

    public static Thread getThread() {
        return thread;
    }

    public static boolean isPause() {
        return pause;
    }
}

項(xiàng)目完整代碼如上。

總結(jié)

本項(xiàng)目基本實(shí)現(xiàn)了抓包的功能涨冀,但是因?yàn)樽龅谋容^趕填硕,所以還有很多功能沒有完善。比如演示的時候發(fā)現(xiàn)有的同學(xué)有選擇網(wǎng)卡的功能鹿鳖,我這里只做了查看網(wǎng)卡的功能扁眯,但是實(shí)現(xiàn)這個功能還是不難的,就是在devices[device]中想辦法能夠通過選擇網(wǎng)卡與device(int)值對應(yīng)翅帜。另外存在的問題就是界面比較簡單姻檀,不是很美觀,然后沒有實(shí)現(xiàn)點(diǎn)開每一個數(shù)據(jù)包能夠查看具體信息的功能涝滴,均還有待完善绣版。

CSDN文章鏈接:https://blog.csdn.net/twj1248445531/article/details/110926300(這是我CSDN里寫的,有目錄歼疮,簡書好像不行杂抽,不支持網(wǎng)頁標(biāo)簽)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市腋妙,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌讯榕,老刑警劉巖骤素,帶你破解...
    沈念sama閱讀 216,997評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件匙睹,死亡現(xiàn)場離奇詭異,居然都是意外死亡济竹,警方通過查閱死者的電腦和手機(jī)痕檬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來送浊,“玉大人梦谜,你說我怎么就攤上這事∠埃” “怎么了唁桩?”我有些...
    開封第一講書人閱讀 163,359評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長耸棒。 經(jīng)常有香客問我荒澡,道長,這世上最難降的妖魔是什么与殃? 我笑而不...
    開封第一講書人閱讀 58,309評論 1 292
  • 正文 為了忘掉前任单山,我火速辦了婚禮,結(jié)果婚禮上幅疼,老公的妹妹穿的比我還像新娘米奸。我一直安慰自己,他們只是感情好爽篷,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,346評論 6 390
  • 文/花漫 我一把揭開白布悴晰。 她就那樣靜靜地躺著,像睡著了一般狼忱。 火紅的嫁衣襯著肌膚如雪膨疏。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,258評論 1 300
  • 那天钻弄,我揣著相機(jī)與錄音佃却,去河邊找鬼。 笑死窘俺,一個胖子當(dāng)著我的面吹牛饲帅,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播瘤泪,決...
    沈念sama閱讀 40,122評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼灶泵,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了对途?” 一聲冷哼從身側(cè)響起赦邻,我...
    開封第一講書人閱讀 38,970評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎实檀,沒想到半個月后惶洲,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體按声,經(jīng)...
    沈念sama閱讀 45,403評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,596評論 3 334
  • 正文 我和宋清朗相戀三年恬吕,在試婚紗的時候發(fā)現(xiàn)自己被綠了签则。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,769評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡铐料,死狀恐怖渐裂,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情钠惩,我是刑警寧澤柒凉,帶...
    沈念sama閱讀 35,464評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站妻柒,受9級特大地震影響扛拨,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜举塔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,075評論 3 327
  • 文/蒙蒙 一绑警、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧央渣,春花似錦计盒、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至拔第,卻和暖如春咕村,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蚊俺。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評論 1 269
  • 我被黑心中介騙來泰國打工懈涛, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人泳猬。 一個月前我還...
    沈念sama閱讀 47,831評論 2 370
  • 正文 我出身青樓批钠,卻偏偏與公主長得像,于是被迫代替她去往敵國和親得封。 傳聞我的和親對象是個殘疾皇子埋心,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,678評論 2 354

推薦閱讀更多精彩內(nèi)容