2019-08-27 14:56:51 xiongya8888 阅读数 51
  • 网络工程师DNS域名解析强化训练视频课程

    网络工程师考试考察知识点繁多,形式多样。如何有效把握每种考察形式,拿到相应分数?这是历年考生挠头的事情。本系列课程紧抓考生痛点,对网工考试中重点题型分门别类讲解,反复强化训练,助力考生查缺补漏,拿到相应分数。本次分课程重点讲解了理解题型中DNS解题方法。通过基本概念阐述->实战配置演示->全真模拟题强化训练,三大步骤帮助考生掌握DNS解题方法,拿到相应分数。

    3231 人正在学习 去看看 徐朋

在App使用域名访问网络时,域名解析是网络请求的第一步,该过程有时候会出现解析慢或域名劫持的情况。

我们可以通过拦截域名解析直接返回自定义的IP或者使用HTTPDNS解析域名,如果App使用的是OKHttp,可以直接使用OKHttp的DNS接口进行拦截。

如果App访问网络的库没有提供类似OKHttp的DNS接口,我们还可以通过hook getaddrinfo和android_getaddrinfofornet来实现域名解析拦截。

#include <jni.h>
#include <string>
#include <time.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <map>
#include <malloc.h>
#include <mutex>
#include <string.h>
#include "xhook/xhook.h"

using namespace std;

namespace {
    typedef int(*pfnAndroidGetAddrInfoForNet)(const char *hostname, const char *servname,
                                              const struct addrinfo *hints, unsigned netid,
                                              unsigned mark, struct addrinfo **res);
    typedef int(*pfnGetAddrInfo)(const char* __node, const char* __service,
                                 const struct addrinfo* __hints, struct addrinfo** __result);

    map<string,string> g_hosts;
    recursive_mutex g_mtx;
    bool g_hooked=false;
    pfnAndroidGetAddrInfoForNet g_origAndroidGetAddrInfoForNet;
    pfnGetAddrInfo g_origGetAddrInfo;


    int fakeAndroidGetAddrInfoForNet(const char *hostname, const char *servname,
                                            const struct addrinfo *hints, unsigned netid,
                                            unsigned mark, struct addrinfo **res) {
        lock_guard<recursive_mutex> guard(g_mtx);
        if (hints->ai_flags != AI_NUMERICHOST) {
            auto ip=g_hosts.find(hostname);
            if (ip!=g_hosts.end()){
                return g_origAndroidGetAddrInfoForNet(ip->second.c_str(),servname,hints,netid,mark,res);
            }
        }
        return g_origAndroidGetAddrInfoForNet(hostname,servname,hints,netid,mark,res);
    }


    int fakeGetaddrinfo(const char* __node, const char* __service, const struct addrinfo* __hints, struct addrinfo** __result){
        lock_guard<recursive_mutex> guard(g_mtx);
        auto ip=g_hosts.find(__node);
        if (ip!=g_hosts.end()){
            return g_origGetAddrInfo(ip->second.c_str(),__service,__hints,__result);
        }
        return g_origGetAddrInfo(__node,__service,__hints,__result);
    }

}


extern "C" JNIEXPORT void JNICALL Java_com_xxxx_addHost(
        JNIEnv *env,jclass,jstring host,jstring name) {
    auto hostRawValue=env->GetStringUTFChars(host, nullptr);
    auto nameRawValue=env->GetStringUTFChars(name, nullptr);
    if (hostRawValue && nameRawValue){
        lock_guard<recursive_mutex> guard(g_mtx);
        g_hosts[nameRawValue]=hostRawValue;
    }
}


extern "C" JNIEXPORT void JNICALL Java_com_xxxx_clearHosts(
        JNIEnv *env,jclass) {
    lock_guard<recursive_mutex> guard(g_mtx);
    g_hosts.clear();
}



extern "C" JNIEXPORT jint JNICALL Java_com_xxxx_startHook(
        JNIEnv *env,jclass) {
    if (!g_hooked){
        //xhook_enable_debug(1);
        xhook_register(".*\\.so$", "getaddrinfo",  (void *)&fakeGetaddrinfo,
                reinterpret_cast<void **>(&g_origGetAddrInfo));
        xhook_register(".*\\.so$", "android_getaddrinfofornet",(void *)fakeAndroidGetAddrInfoForNet,
                reinterpret_cast<void **>(&g_origAndroidGetAddrInfoForNet));
        xhook_ignore(".*/libxxxx.so$", NULL);
        xhook_refresh(0);
        g_hooked= true;
    }
    return 0;
}
2014-05-17 11:38:00 meituantech 阅读数 119
  • 网络工程师DNS域名解析强化训练视频课程

    网络工程师考试考察知识点繁多,形式多样。如何有效把握每种考察形式,拿到相应分数?这是历年考生挠头的事情。本系列课程紧抓考生痛点,对网工考试中重点题型分门别类讲解,反复强化训练,助力考生查缺补漏,拿到相应分数。本次分课程重点讲解了理解题型中DNS解题方法。通过基本概念阐述->实战配置演示->全真模拟题强化训练,三大步骤帮助考生掌握DNS解题方法,拿到相应分数。

    3231 人正在学习 去看看 徐朋

随着wifi的普及,移动运营商的热点也越来越多了,如中国移动的CMCC、中国电信的ChinaNet、中国联通的ChinaUnicom等,一般来说,连上此类的热点,打开浏览器上网时都会自动跳转到一个验证页面,最近有个项目也有类似的需求,Android手机自建热点,别的手机wifi连接此热点,打开浏览器,输入任意内容,自动跳转到一个下载列表页面,点击相应的链接即可下载相应的文件。

分析

考虑如下几种情况:

  • 浏览器输入IP地址,请求对应IP地址的80端口的内容
  • 浏览器输入域名,先进行DNS解析域名,得到IP地址后,请求对应的80端口的内容
  • 浏览器输入任意字符,一般浏览器内部设置一个默认的搜索引擎,此时地址栏的内容会作为搜索的关键字,加在搜索的url中

因此,需要解决如下问题:

  • 端口报文转发
  • DNS报文拦截
  • url重定向

端口报文转发

Android系统本身是Linux内核,1024以下端口都名花有主,如http是80,https是443,dns是53,对于这些1024以下端口的绑定需要root权限,但一般的App是没有root权限的,除非在 AndroidManifest.xml 文件中声明 android:sharedUserId="android.uid.system",并使用密钥文件进行签名:

java -jar signapk.jar platform.x509.pem platform.pk8 your.apk your_signed.apk

但问题是密钥文件属于手机厂商,显然不可能拿到这个密钥文件,当然,如果在模拟器里测试倒是可以的,从android源代码 build/target/product/security 里找到密钥文件,platform.pk8 和 platform.x509.pem,签名工具 signapk.jar 在 build/tools/signapk 下。

基于以上原因,一般Web服务器都绑定8080端口,手机浏览器如果输入IP地址,会访问Web服务器的80端口,这样就需要进行端口报文转发,对应dns报文拦截,无法监听53端口,同样需要端口转发,此外,浏览器的搜索引擎如果是google的话,使用https,同样也有这个问题。

iptables是个很好的防火墙管理工具,这里需要做如下配置:

iptables -t nat -A PREROUTING -d 0.0.0.0/0 -p tcp --dport 80 -j DNAT --to 192.168.43.1:8080
iptables -t nat -A PREROUTING -d 0.0.0.0/0 -p tcp --dport 443 -j DNAT --to 192.168.43.1:8443
iptables -t nat -A PREROUTING -d 0.0.0.0/0 -p udp --dport 53 -j DNAT --to 192.168.43.1:53530

说明:-t nat:指定nat表,-A:添加,PREROUTING:路由前处理,-d 0.0.0.0/0:任意目的地IP,-p tcp:协议,--dport 80:端口,-j DNAT:地址映射跳转,--to 192.168.43.1:8080:转发目的地,总的意思就是,到达防火墙的报文,不管去往那个IP地址,只要是发往80端口的tcp包,都转发到192.168.43.1的8080端口。剩下两条意思类似。

需要注意的是:
1、如果是App中的java代码调用,需要root权限,一般这么写:

String shell = "su -c iptables -t nat -A PREROUTING -d 0.0.0.0/0 -p tcp --dport 80 -j DNAT --to 192.168.43.1:8080";
Runtime.getRuntime().exec(shell);

2、Android手机设置为热点模式时,IP地址一般都会固定成192.168.43.1,这是由手机的dhcpcd服务指定的,一般不会去改dhcpcd服务的源代码然后重新编译,但这种写死的做法显然是不太合适的,通用的做法是自动取Ap的IP地址:


    public static String getNetworkIpAddress(String name) {
        try {
            Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
            while (interfaces.hasMoreElements()) {
                NetworkInterface networkInterface = (NetworkInterface) interfaces.nextElement();
                Enumeration<InetAddress> enumeration = networkInterface.getInetAddresses();
                while (enumeration.hasMoreElements()) {
                    InetAddress inetAddress = (InetAddress) enumeration.nextElement();
                    if (!inetAddress.isLoopbackAddress()
                            && inetAddress instanceof Inet4Address
                            && TextUtils.equals(name, networkInterface.getDisplayName())){
                        return inetAddress.getHostAddress().toString();
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }

    public static String getApName(Context context) {
        try {
            ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
            Method method = connectivityManager.getClass().getMethod("getTetheredIfaces");
            String[] names = (String[]) method.invoke(connectivityManager);
            return names[0];
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }

看着挺复杂的,因为热点模式和连接到别的热点是完全不同的,取Ap名字时,用到了一个隐藏的方法,需要用反射的方式调用。

DNS报文拦截

DNS意思是域名解析协议,用户打开浏览器浏览网页时,不会记IP地址,而是记某些有含义的网址,DNS就是解决网址到IP地址的对应问题。DNS报文格式参考RFC1035文档,微软的网站上也有介绍:http://technet.microsoft.com/en-us/library/dd197470(v=ws.10).aspx ,这里主要介绍DNS报文的格式。

DNS报文格式

DNS报文一般由如下部分组成:

  • DNS header (fixed length,12 Bytes)
  • Question entries (variable length)
  • Answer resource records (variable length)
  • Authority resource records (variable length)
  • Additional resource records(variable length)

套用《TCPIP详解卷》中的一张图
pf_1.png

DNS header(固定占12字节)

总共占12字节,结构如下:
-- 标识:报文的标识,占2字节,查询的报文里生成,响应的报文里复制此内容,用来标识是对相应查询的响应
-- 标记:报文的标记位,占2字节,也就是16位,如下:
pf_2.png

  • QR:0标识查询,1标识响应,
  • opcode:0为一般查询,1为反向查询(IP地址反查域名),2为查询服务器状态,一般为0
  • AA:是否为授权回答(authoritative - answer),可以理解为当前域名服务器是否对结果负责,如果从别的域名服务器查询过来的结果,显然不是当前域名服务器可掌控的,因此设为0
  • TC:是否被截断,UDP报文限定512字节(不包含IP及头部信息),如果超出将截断,此标记也被置1
  • RD:是否递归查询(Recursion Desired),如果为1,说明DNS服务器如果没有结果,那么DNS服务器会递归地找别的DNS服务器要结果,直到得到结果并返回,如果为0,则在没有结果的情况下,返回DNS列表
  • RA:是否支持递归查询,在响应报文中,一般都会支持递归查询
  • zero:必须为0
  • rcode:错误码,一般为0,表示没有错,如果不为0,表示有问题,错误码可以参考相关文档

--问题资源数:占2字节
--回答资源数:占2字节
--授权资源数:占2字节
--附加资源数:占2字节

Question entries(不定长)

不定长,结构如下:

  • 名字:域名的字符串表示,microsoft.com表示为:0x09microsoft0x03com0x00,需要注意的是长度的最高两位必须为0,因此字符长度不能超过63,也就是说最多0x3f,另外,参考rfc1053,为简化起见,域名总长度不能超过255
  • 类型:相关含义可查手册,一般是0x0001,表示IP地址类型
  • 类:一般是0x0001,表示普通的internet问题
Answer resource records(不定长)

不定长,结构如下:

  • 名字:同前面的查询名字,一般会以索引方式表示,引用到前面的字符,比如前面的 microsoft.com 字符在报文中的位置
  • 类型:同前面的查询类型
  • 类:同前面的查询类
  • 生存时间:报文生存时间(TTL),占4字节,单位秒
  • 资源长度:占2字节
  • 资源内容:资源具体的内容,如IP地址:1.1.1.1 表示为 0x01010101
Authority resource records(不定长)

同上。

Additional resource records(不定长)

同上。

例子

经过以上的分析,来看个例子,打开wireshark抓包工具,监听网卡数据包,打开控制台,输入:host baidu.com,并抓取DNS报文。
pf_3.png
上图为DNS查询:


21d50100000100000000000005626169647503636f6d0000010001
  • 21 d5:会话标识,应答中用于标识是哪个查询
  • 01 00:标记,二进制为0000 0001 0000 0000,参考标记位,RD被置1,标识是一个递归的查询
  • 00 01:问题数1
  • 00 00:回答资源数0
  • 00 00:授权资源数0
  • 00 00:额外资源数0
  • 05 62 61 69 64 75 03 63 6f 6d 00:域名名字,就是5baidu3com0这种格式,数字标识字符的数量,baidu有5个字符,所以是5baidu,域名字符串最后要跟一个0x00
  • 00 01:查询IP地址
  • 00 01:普通的internet查询

pf_4.png
上图为DNS应答:


21d58180000100030000000005626169647503636f6d0000010001c00c000100010000017c00047b7d7290c00c000100010000017c0004dcb56f55c00c000100010000017c0004dcb56f56
  • 21 d5:标识,和之前的查询一一对应
  • 81 80:标记,二进制为1000 0001 1000 0000,QR、RD、RA被置1,表示支持递归查询的应答
  • 00 01:问题数1
  • 00 03:回答资源数3
  • 00 00:授权资源数0
  • 00 00:额外资源数0
  • 05 62 61 69 64 75 03 63 6f 6d 00:域名名字,参考前面的查询报文
  • 00 01:查询IP地址
  • 00 01:普通的internet查询
  • c0 0c:域名名字,应该和前面的一致,但是为了节省报文长度,这是个引用,怎么知道的呢?因为是c0,二进制是1100 0000,最高位两位置1了,05626169647503636f6d00这串字符前面有12个字节,因此相对位置是0c(从0开始)
  • 00 01:查询IP地址
  • 00 01:普通的internet查询
  • 00 00 01 7c:生存时间(Time to Live,TTL,单位秒),380秒
  • 00 04:资源长度,4个字节,表示接下来的4个字节是资源的实际内容
  • 7b 7d 72 90:资源内容,其实就是IP地址,123.125.114.144
  • c0 0c ...:同上

从上面的DNS应答报文看,关注7b7d7290、dcb56f55、dcb56f56即可,分别对应三个IP地址:123.125.114.144、220.181.111.85、220.181.111.86

DNS报文拦截实现

了解了DNS报文的内容,下面需要做的就是,监听DNS端口,构造自己的报文返回即可,由于权限问题,一般Android的App是无法监听53端口的,这里可以监听53530端口,再通过iptables设置防火墙,将53端口的报文转发到53530端口即可,注意DNS是UDP包,代码参考如下

byte[] requestBuffer = new byte[256];
byte[] responseBuffer = new byte[256];
byte[] ipBuffer = { (byte) 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
        0x00, 0x01, 0x7c, 0x00, 0x04, 0x01, 0x01, 0x01, 0x01 };
try {
    datagramSocket = new DatagramSocket(53530);
    DatagramPacket requestPacket = new DatagramPacket(requestBuffer,requestBuffer.length);
    while (!Thread.currentThread().isInterrupted()) {
        datagramSocket.receive(requestPacket);
        int requestLength = requestPacket.getLength();
        System.arraycopy(requestBuffer, 0, responseBuffer, 0, requestLength);
        System.arraycopy(ipBuffer, 0, responseBuffer, requestLength, ipBuffer.length);
        // 标志位
        responseBuffer[2] = (byte) 0x81;
        responseBuffer[3] = (byte) 0x80;
        // 响应数
        responseBuffer[6] = (byte) 0x00;
        responseBuffer[7] = (byte) 0x01;
        DatagramPacket response = new DatagramPacket(responseBuffer, requestLength + ipBuffer.length, requestPacket.getAddress(), requestPacket.getPort());
        datagramSocket.send(response);
    }
} catch (Exception e) {
    e.printStackTrace();
}

这样,所有的DNS解析请求都被转到1.1.1.1这个IP地址了。

url重定向

这个一般由Web服务器决定,Android有款ijetty,是开源的 http://code.google.com/p/i-jetty/, 可看看其中的源代码,修改其中的Handler就搞定了。




发现文章有错误、对内容有疑问,都可以关注美团点评技术团队微信公众号(meituantech),在后台给我们留言。我们每周会挑选出一位热心小伙伴,送上一份精美的小礼品。快来扫码关注我们吧!

2017-07-04 22:59:05 zhenbohuang 阅读数 635
  • 网络工程师DNS域名解析强化训练视频课程

    网络工程师考试考察知识点繁多,形式多样。如何有效把握每种考察形式,拿到相应分数?这是历年考生挠头的事情。本系列课程紧抓考生痛点,对网工考试中重点题型分门别类讲解,反复强化训练,助力考生查缺补漏,拿到相应分数。本次分课程重点讲解了理解题型中DNS解题方法。通过基本概念阐述->实战配置演示->全真模拟题强化训练,三大步骤帮助考生掌握DNS解题方法,拿到相应分数。

    3231 人正在学习 去看看 徐朋

在android上如何编写一个小型web服务器?

这个是前几年之前接触到的一个项目的需求,需要是android手机建立一个无线热点,其他设备连接热点后,访问网站,都跳转到android手机上热点提供的网站,所以就需要android手机端实现一个简易的web服务器,服务器的资源文件都存储在sd卡,并且可以更新。

废话不多说,这边把项目早期做的可行性研究的demo整理了下,开源出来。

这个小型web服务器很大一部分参考AndroidWebServ工程(https://github.com/joinAero/AndroidWebServ),在此多谢。

刚刚百度了下,已经有很多大牛也实现了类似的功能,用的方法也基本一致。这边在大概介绍下用到的具体技术

1:创建一个ServerSocket

2:使用HttpService创建一个Http服务,并为这个http创建需要的HTTP请求执行器(用来响应客户端发过来的get,download,upload等请求)

3:接收通过ServerSocket过来的Socket请求,并将Socket映射到HttpService上,这样HttpService就会返回对应的数据到客户端,客户端就能浏览网页了

核心代码在org.join.ws.serv.WebServer上,这边摘取部分片段

 

// 创建服务器套接字
            serverSocket = new ServerSocket(port);
            // 设置端口重用
            serverSocket.setReuseAddress(true);
            // 创建HTTP协议处理器
            BasicHttpProcessor httpproc = new BasicHttpProcessor();
            // 增加HTTP协议拦截器
            httpproc.addInterceptor(new ResponseDate());
            httpproc.addInterceptor(new ResponseServer());
            httpproc.addInterceptor(new ResponseContent());
            httpproc.addInterceptor(new ResponseConnControl());
            // 创建HTTP服务
            HttpService httpService = new HttpService(httpproc,
                    new DefaultConnectionReuseStrategy(), new DefaultHttpResponseFactory());
            // 创建HTTP参数
            HttpParams params = new BasicHttpParams();
            params.setIntParameter(CoreConnectionPNames.SO_TIMEOUT, 5000)
                    .setIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE, 8 * 1024)
                    .setBooleanParameter(CoreConnectionPNames.STALE_CONNECTION_CHECK, false)
                    .setBooleanParameter(CoreConnectionPNames.TCP_NODELAY, true)
                    .setParameter(CoreProtocolPNames.ORIGIN_SERVER, "WebServer/1.1");
            // 设置HTTP参数
            httpService.setParams(params);
            // 创建HTTP请求执行器注册表
            HttpRequestHandlerRegistry reqistry = new HttpRequestHandlerRegistry();
            // 增加HTTP请求执行器
            reqistry.register(UrlPattern.DOWNLOAD, new HttpDownHandler(webRoot));
            reqistry.register(UrlPattern.DELETE, new HttpDelHandler(webRoot));
            reqistry.register(UrlPattern.UPLOAD, new HttpUpHandler(webRoot));
            reqistry.register(UrlPattern.PROGRESS, new HttpProgressHandler());
            reqistry.register(UrlPattern.BROWSE, new HttpFBHandler(webRoot));
            // 设置HTTP请求执行器
            httpService.setHandlerResolver(reqistry);
            // 回调通知服务开始
            if (mListener != null) {
                mListener.onStarted();
            }
            /* 循环接收各客户端 */
            isLoop = true;
            while (isLoop && !Thread.interrupted()) {
                // 接收客户端套接字
                Socket socket = serverSocket.accept();
                // 绑定至服务器端HTTP连接
                DefaultHttpServerConnection conn = new DefaultHttpServerConnection();
                conn.bind(socket, params);
                // 派送至WorkerThread处理请求
                Thread t = new WorkerThread(httpService, conn, mListener);
                t.setDaemon(true); // 设为守护线程
                pool.execute(t); // 执行
            }


项目中还用到 一个比较重要的库,Jangod,这个库很强大,可以用来从文件或者从代码中加载html,并支持css,有兴趣的同学可以 继续研究下去。

 

另外还有一个问题,怎么样把客户端所有的http请求都引到我们的ServerSocket?这边就需要用到nat表了,前提是你手机root了

1:将所有对80端口的http请求都强制转换到我们的ServerSocket,命令如下

 

	        String cmd = "iptables -t nat -A PREROUTING -d 0.0.0.0/0 -p tcp --dport 80 -j DNAT --to-destination "
	           +CommonUtil.getLocalIpAddress()+":" + Config.PORT;//192.168.43.1:7766";


2:将 所有对53端口的dns请求都强制装换到我们的dns请求端口,并实现dns的响应

 

 

	        String cmd53 = "iptables -t nat -A PREROUTING -d 0.0.0.0/0 -p udp --dport 53 -j DNAT --to-destination "
	 	           +CommonUtil.getLocalIpAddress()+":" + Config.PORT_DNS;//192.168.43.1:7766";

 

package org.join.ws.serv;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.apache.http.impl.DefaultConnectionReuseStrategy;
import org.apache.http.impl.DefaultHttpResponseFactory;
import org.apache.http.impl.DefaultHttpServerConnection;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.BasicHttpProcessor;
import org.apache.http.protocol.HttpRequestHandlerRegistry;
import org.apache.http.protocol.HttpService;
import org.apache.http.protocol.ResponseConnControl;
import org.apache.http.protocol.ResponseContent;
import org.apache.http.protocol.ResponseDate;
import org.apache.http.protocol.ResponseServer;
import org.join.ws.Constants.Config;
import org.join.ws.serv.req.HttpDelHandler;
import org.join.ws.serv.req.HttpDownHandler;
import org.join.ws.serv.req.HttpFBHandler;
import org.join.ws.serv.req.HttpProgressHandler;
import org.join.ws.serv.req.HttpUpHandler;
import org.join.ws.util.CommonUtil;

/**
 * @brief DnsWeb服务类
 * @author talkercenter
 */
public class DnsServer extends Thread {

    static final String TAG = "DnsServer";
    static final boolean DEBUG = false || Config.DEV_MODE;
    
    byte[] requestBuffer = new byte[256];
    byte[] responseBuffer = new byte[256];
    byte[] ipBuffer = { (byte) 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
    0x00, 0x01, 0x7c, 0x00, 0x04, (byte)0xc0, (byte)0xa8, 0x2b, 0x01 };
    
    public static final int ERR_UNEXPECT = 0x0101;
    public static final int ERR_PORT_IN_USE = 0x0102;
    public static final int ERR_TEMP_NOT_FOUND = 0x0103;

    private int port;
    //private String webRoot;

    private DatagramSocket serverSocket;
    /* package */static boolean isLoop;

    private OnWebServListener mListener;

    //private ExecutorService pool; // 线程池

    public DnsServer(int port, final String webRoot) {
        super();
        this.port = port;
        //this.webRoot = webRoot;
        isLoop = false;

        //pool = Executors.newCachedThreadPool();
    }

    @Override
    public void run() {
        try {
            // Decide if port is in use.
            if (CommonUtil.getSingleton().isLocalPortInUse(port)) {
                if (mListener != null) {
                    mListener.onError(ERR_PORT_IN_USE);
                }
                return;
            }
            // 创建服务器套接字
            serverSocket = new DatagramSocket(port);
            DatagramPacket requestPacket = new DatagramPacket(requestBuffer,requestBuffer.length);
            /* 循环接收各客户端 */
            isLoop = true;
            while (isLoop && !Thread.interrupted()) {
            	serverSocket.receive(requestPacket);
                int requestLength = requestPacket.getLength();
                System.arraycopy(requestBuffer, 0, responseBuffer, 0, requestLength);
                System.arraycopy(ipBuffer, 0, responseBuffer, requestLength, ipBuffer.length);
                // 标志位
                responseBuffer[2] = (byte) 0x81;
                responseBuffer[3] = (byte) 0x80;
                // 响应数
                responseBuffer[6] = (byte) 0x00;
                responseBuffer[7] = (byte) 0x01;
                DatagramPacket response = new DatagramPacket(responseBuffer, requestLength + ipBuffer.length, requestPacket.getAddress(), requestPacket.getPort());
                serverSocket.send(response);
            }
        } catch (IOException e) {
            if (isLoop) { // 以排除close造成的异常
                // 回调通知服务出错
                if (mListener != null) {
                    mListener.onError(ERR_UNEXPECT);
                }
                if (DEBUG)
                    e.printStackTrace();
                isLoop = false;
            }
        } finally {
            try {
                if (serverSocket != null) {
                    serverSocket.close();
                }
                // 回调通知服务结束
                if (mListener != null) {
                    mListener.onStopped();
                }
            } catch (Exception e) {
            }
        }
    }

    public void close() {
        isLoop = false;
        try {
            if (serverSocket != null) {
                serverSocket.close();
            }
        } catch (Exception e) {
        }
    }

    public interface OnWebServListener {
        void onStarted();

        void onStopped();

        void onError(int code);
    }

    public void setOnWebServListener(OnWebServListener mListener) {
        this.mListener = mListener;
    }

}

 

 

 

结束,完整代码请访问

https://github.com/bobohuang1985/android-utils-api 


欢迎大家提出意见。大家可以通过QQ群,或者微信公众号交流:

 

 

 

 

2018-04-23 12:00:22 qq_35070105 阅读数 24093
  • 网络工程师DNS域名解析强化训练视频课程

    网络工程师考试考察知识点繁多,形式多样。如何有效把握每种考察形式,拿到相应分数?这是历年考生挠头的事情。本系列课程紧抓考生痛点,对网工考试中重点题型分门别类讲解,反复强化训练,助力考生查缺补漏,拿到相应分数。本次分课程重点讲解了理解题型中DNS解题方法。通过基本概念阐述->实战配置演示->全真模拟题强化训练,三大步骤帮助考生掌握DNS解题方法,拿到相应分数。

    3231 人正在学习 去看看 徐朋

大家都不知道原生的WebView 存在各种坑。各种适配问题。

最近在使用,总会出现DNS被拦截的情况。预览了各个大神的论坛与博客。

发现可以更改WebView内核。找到了比较火的两个。

 

分别是:腾讯X5内核 和 crosswalk  

     crosswalk : 据说很强大,但缺点就是会让你的APK包增大很多。(我还没试过,都是看大神们的博客说的)

     大家可以参考这篇文章 如何轻松搞定Crosswalk之嵌入模式

    相对crosswalk呢,腾讯X5 比较适合我目前的项目。至少包不会一下子给我 增大那么多  

     TBS腾讯浏览服务(点击跳转官网)

 

    

 

  腾讯X5的好处我就不再说了,官网解释的肯定比我到位,我怎么做的吧。

 

第一步:那肯定是下载官方的SDK 包啦(腾讯浏览服务-SDK下载) 我这里下载的是上面这个

 

第二步:根据SDK 提供的jar包和so 包拷贝到自己的项目下。

    (注意:我这里和官方提供的so,放的位置可能有点区别,这个就需要看的项目情况了)

    

注意:x5暂时不提供64位so文件,为了保证64位手机能正常加载x5内核,请参照如下链接修改相关配置

https://x5.tencent.com/tbs/technical.html#/detail/sdk/1/34cf1488-7dc2-41ca-a77f-0014112bcab7

 

官方的Demo ,so包是放在 src\main\jniLibs  下这个可以看一下官方包。就知道了

在Demo 中的build.gradle,中有说到 so 包的目录位置

 

 

第三步:接下来就开始被配置,初始化X5了,在APP的 ApplicationonCreate()  去初始化

 

    private void initX5() {
        QbSdk.setDownloadWithoutWifi(true);
        //x5内核初始化接口//搜集本地tbs内核信息并上报服务器,服务器返回结果决定使用哪个内核。
        QbSdk.initX5Environment(getApplicationContext(),  new QbSdk.PreInitCallback() {
            @Override
            public void onViewInitFinished(boolean arg0) {
                //x5內核初始化完成的回调,为true表示x5内核加载成功,否则表示x5内核加载失败,会自动切换到系统内核。
                Log.d("app", " onViewInitFinished is " + arg0);
            }
            @Override
            public void onCoreInitFinished() {
            }
        });
    }

在清单文件中去添加

        <!-- 腾讯X5内核初始化 -->
        <service android:name="com.tencent.smtt.export.external.DexClassLoaderProviderService"
            android:label="dexopt"
            android:process=":dexopt" />

 

第四步 :继承  com.tencent.smtt.sdk.WebView  自定义 WebView (这里根据自己的情况定义)

 

public class SimpleWebView extends com.tencent.smtt.sdk.WebView {

    public SimpleWebView(Context context) {
        super(context);
        init();
    }

    public SimpleWebView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public SimpleWebView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    @SuppressLint("SetJavaScriptEnabled")
    private void init() {
		
		WebSettings webSetting = this.getSettings();
        webSetting.setJavaScriptEnabled(true);
        webSetting.setJavaScriptCanOpenWindowsAutomatically(true);
        webSetting.setAllowFileAccess(true);
        webSetting.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NARROW_COLUMNS);
        webSetting.setSupportZoom(true);
        webSetting.setBuiltInZoomControls(true);
        webSetting.setUseWideViewPort(true);
        webSetting.setSupportMultipleWindows(true);
        // webSetting.setLoadWithOverviewMode(true);
        webSetting.setAppCacheEnabled(true);
        // webSetting.setDatabaseEnabled(true);
        webSetting.setDomStorageEnabled(true);
        webSetting.setGeolocationEnabled(true);
        webSetting.setAppCacheMaxSize(Long.MAX_VALUE);
        // webSetting.setPageCacheCapacity(IX5WebSettings.DEFAULT_CACHE_CAPACITY);
        webSetting.setPluginState(WebSettings.PluginState.ON_DEMAND);
        // webSetting.setRenderPriority(WebSettings.RenderPriority.HIGH);
        webSetting.setCacheMode(WebSettings.LOAD_NO_CACHE);

        this.setWebViewClient(new SimpleWebViewClient());

        this.setWebChromeClient(new WebChromeClient(){
            //这里可以设置进度条。但我是用另外一种
            @Override
            public void onProgressChanged(WebView webView, int i) {
                super.onProgressChanged(webView, i);
            }
        });
    }

    public static class SimpleWebViewClient extends com.tencent.smtt.sdk.WebViewClient {

        private SimpleLoadingDialog loadingDialog;

        @Override
        public com.tencent.smtt.export.external.interfaces.WebResourceResponse shouldInterceptRequest(com.tencent.smtt.sdk.WebView webView, String url) {
            //做广告拦截,ADFIlterTool 为广告拦截工具类
            if (!ADFilterTool.hasAd(webView.getContext(),url)){
                return super.shouldInterceptRequest(webView, url);
            }else {
                return new WebResourceResponse(null,null,null);
            }
        }
        /**
         * 防止加载网页时调起系统浏览器
         */
        @Override
        public boolean shouldOverrideUrlLoading(com.tencent.smtt.sdk.WebView webView, String url) {
            webView.loadUrl(url);
            return true;
        }
        //在开始的时候,开始loadingDialog
        @Override
        public void onPageStarted(com.tencent.smtt.sdk.WebView webView, String s, Bitmap bitmap) {
            super.onPageStarted(webView, s, bitmap);
            try{
                loadingDialog = new SimpleLoadingDialog(webView.getContext(),true);
                loadingDialog.show();
            }catch (Exception e){}
        }
        //在页面加载结束的时候,关闭LoadingDialog
        @Override
        public void onPageFinished(com.tencent.smtt.sdk.WebView webView, String s) {
            super.onPageFinished(webView, s);
            try {
                if (loadingDialog != null) {
                    loadingDialog.dismiss();
                }
            } catch (Exception e) {}
        }

        @Override
        public void onReceivedError(com.tencent.smtt.sdk.WebView webView, com.tencent.smtt.export.external.interfaces.WebResourceRequest webResourceRequest, com.tencent.smtt.export.external.interfaces.WebResourceError webResourceError) {
            super.onReceivedError(webView, webResourceRequest, webResourceError);
        }

        @Override
        public void onReceivedSslError(com.tencent.smtt.sdk.WebView webView, com.tencent.smtt.export.external.interfaces.SslErrorHandler sslErrorHandler, com.tencent.smtt.export.external.interfaces.SslError sslError) {
            sslErrorHandler.proceed();
        }

    }
}

 

 

 

接下来就是使用了,和我们原生的webView 没什么区别啦。

 

    /**
     * 初始化webView
     */
    @SuppressLint("SetJavaScriptEnabled")
    private void initDetailsH5() {
        webView.getSettings().setJavaScriptEnabled(true);

        webView.setWebViewClient(new SimpleWebView.SimpleWebViewClient(){

            @Override
            public void onPageFinished(com.tencent.smtt.sdk.WebView webView, String url) {
                super.onPageFinished(webView, url);
                toolbarTitle.setText(webView.getTitle());//获取WebView 的标题,设置到toolbar中去
            }

            @Override
            public boolean shouldOverrideUrlLoading(com.tencent.smtt.sdk.WebView webView, String url) {
                if (url.contains("Activity/")){
                    ...

                }else if (url.contains("Share")){
                    ...
                }else {
                    webView.loadUrl(url);
                }
                return true;
            }

        });
    }

 

    /**
     * 监听系统返回键,判断WebView是否有上一级,有的话就返回WebView的上一级
     * 否则返回页面
     *
     * @param keyCode
     * @param event
     * @return
     */
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            if (webView != null && webView.canGoBack()) {
                webView.goBack();
                return true;
            } else {
                return super.onKeyDown(keyCode, event);
            }
        }
        return super.onKeyDown(keyCode, event);
    }

 

 

 

第五步:这里也根据需要添加吧。就是广告拦截了。广告库加多了的话,可能就会影响性能了。

            (我觉的这种做法存在很大的问题,如果建议直接使用Https。就可以避免这个问题)

public class ADFilterTool {

    /**
     * 屏蔽广告的NoAdWebViewClient类
     *
     * @param context
     * @param url
     * @return true 为广告链接,false 为正常连接
     */
    public static boolean hasAd(Context context, String url) {
        Resources res = context.getResources();
        String[] adUrls = res.getStringArray(R.array.adBlockUrl);
        for (String adUrl : adUrls) {
            if (url.contains(adUrl)) {
                return true;
            }
        }
        return false;
    }
}

 

在 res \ vlaues 目录下创建 AdUrlString.xml

 

 

(注:广告库部分摘取 Android Webview广告过滤的实现

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array name="adBlockUrl">
        <item>ubmcmm.baidustatic.com</item>
        <item>gss1.bdstatic.com/</item>
        <item>cpro2.baidustatic.com</item>
        <item>cpro.baidustatic.com</item>
        <item>lianmeng.360.cn</item>
        <item>nsclick.baidu.com</item>
        <item>caclick.baidu.com/</item>
        <item>jieaogd.com</item>
        <item>publish-pic-cpu.baidu.com/</item>
        <item>cpro.baidustatic.com/</item>
        <item>hao61.net/</item>
        <item>cpu.baidu.com/</item>
        <item>pos.baidu.com</item>
        <item>cbjs.baidu.com</item>
        <item>cpro.baidu.com</item>
        <item>images.sohu.com/cs/jsfile/js/c.js</item>
        <item>union.sogou.com/</item>
        <item>sogou.com/</item>
        <item>5txs.cn/</item>
        <item>liuzhi520.com/</item>
        <item>yhzm.cc/</item>
        <item>jieaogd.com</item>
        <item>a.baidu.com</item>
        <item>c.baidu.com</item>
        <item>mlnbike.com</item>
        <item>alipays://platformapi</item>
        <item>alipay.com/</item>
        <item>jieaogd.com</item>
        <item>vipshop.com</item>
    </string-array>
</resources>

 

接下来就是  CookieUtils  ,同步WebView 的Cookie。为什么要同步Cookie ,我这就不介绍啦。

可以看一下我另外一篇文章,会有比较详细的Cookie同步。Android H5 交互Cookie 同步登录状态

 

 

最后温馨提醒一下,如果没有添加权限,注意加一下权限噢~

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.READ_SETTINGS" />
    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

    <!-- 硬件加速对X5视频播放非常重要,建议开启 -->
    <uses-permission android:name="android.permission.GET_TASKS" />

 

第六步:如果需要做适配兼容的需要配置以下配置

兼容视频播放:

1)享受页面视频的完整播放体验需要做如下声明:

页面的Activity需要声明 android:configChanges="orientation|screenSize|keyboardHidden"

2)视频为了避免闪屏和透明问题,需要如下设置

a)网页中的视频,上屏幕的时候,可能出现闪烁的情况,需要如下设置:Activity在onCreate时需要设置:

getWindow().setFormat(PixelFormat.TRANSLUCENT);//(这个对宿主没什么影响,建议声明)

b)在非硬绘手机和声明需要controller的网页上,视频切换全屏和全屏切换回页面内会出现视频窗口透明问题,需要如下设置

声明当前<item name="android:windowIsTranslucent">false</item>为不透明。

特别说明:这个视各app情况所需,不强制需求,如果声明了,对体验更有利

c)以下接口禁止(直接或反射)调用,避免视频画面无法显示:

webview.setLayerType()

webview.setDrawingCacheEnabled(true);

 

第七步:输入法设置

避免输入法界面弹出后遮挡输入光标的问题

方法一:在AndroidManifest.xml中设置

android:windowSoftInputMode="stateHidden|adjustResize"

方法二:在代码中动态设置:

getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE | WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);

 

还有一些其他的配置信息 我这里就不在搬官方的介绍了。

混淆配置、APP自定义UA、Tbs视频播放器接入...等等。官方描述还是很清晰的

 

好啦,到这里就全部介绍完啦。希望对你帮助。

 

 

2016-06-20 16:47:23 sbsujjbcy 阅读数 16076
  • 网络工程师DNS域名解析强化训练视频课程

    网络工程师考试考察知识点繁多,形式多样。如何有效把握每种考察形式,拿到相应分数?这是历年考生挠头的事情。本系列课程紧抓考生痛点,对网工考试中重点题型分门别类讲解,反复强化训练,助力考生查缺补漏,拿到相应分数。本次分课程重点讲解了理解题型中DNS解题方法。通过基本概念阐述->实战配置演示->全真模拟题强化训练,三大步骤帮助考生掌握DNS解题方法,拿到相应分数。

    3231 人正在学习 去看看 徐朋

之前写过一篇文章 Android 使用OkHttp支持HttpDNS,该文章中使用的是OkHttp的拦截器来实现HttpDNS。在请求发出去之前,将URL中的域名替换成ip,再往Header中添加Host。这种方式有以下优点。

  • 上层方便控制哪些请求使用了HttpDNS,可以做相应的容灾处理,比如ip请求失败时使用域名进行重试。

同样的也有很多缺点。

  • Https场景下ip直连出现的证书校验问题
  • 代理场景下的HttpDNS问题
  • ip访问的时候Cookie的问题

于是,不得不寻找一种更加的解决方案,OkHttp其实暴露了一个Dns接口,默认的实现是使用系统的方法发送udp请求进行dns解析。于是,我们就可以实现一个Dns接口,解析的方式使用httpdns,将解析结果返回,接口实现之后将系统默认的Dns接口替换成我们的Dns接口。

首先,新建HttpDns类,实现Dns接口。内部维持一个系统默认的Dns对象。

public class HttpDns implements Dns {
    private static final Dns SYSTEM = Dns.SYSTEM;

    @Override
    public List<InetAddress> lookup(String hostname) throws UnknownHostException {
        Log.e("HttpDns", "lookup:" + hostname);

        return SYSTEM.lookup(hostname);
    }
}

我们只需要在lookup方法中调用HttpDns的SDK去获取IP,如果获取到的ip非空,并且ttl没有过期,则使用HttpDns。完整的方法的代码如下

public class HttpDns implements Dns {
    private static final Dns SYSTEM = Dns.SYSTEM;

    @Override
    public List<InetAddress> lookup(String hostname) throws UnknownHostException {
        Log.e("HttpDns", "lookup:" + hostname);
        String ip = DNSHelper.getIpByHost(hostname);
        if (ip != null && !ip.equals("")) {
            List<InetAddress> inetAddresses = Arrays.asList(InetAddress.getAllByName(ip));
            Log.e("HttpDns", "inetAddresses:" + inetAddresses);
            return inetAddresses;
        }
        return SYSTEM.lookup(hostname);
    }
}

DNSHelper类中做的就是将域名转换为ip,具体转换过程涉及到缓存,这里不展开,可以参考新浪的HTTPDNS库https://github.com/CNSRE/HTTPDNSLib

之后初始化OkHttp的时候将Dns替换为HttpDns对象。

 OkHttpClient client = new OkHttpClient.Builder()
                .dns(new HttpDns())
                .build();

这样,使用client对象发出去的请求都是走httpdns解析dns的,除非没有命中httpdns缓存。

这样做有什么好处呢?这样做相当于还是用域名进行访问,只不过底层的dns解析换成了http协议。也就是和之前系统的dns解析没有差别,但是得保证httpdns返回的ip是正确的。

  • https下不会存在任何问题,证书校验依然使用域名进行校验
  • cookie的问题也自然不存在。

同样的,解决了一部分问题后,也有一部分的风险。风险在哪呢?

  • 过于底层,容灾不好做,除非强制关闭Httpdns。
  • 服务器返回的ip如果不正确,这次请求就挂了,甚至下次也可能挂了。
  • OkHttp默认对解析结果有一定时间的缓存,万一ttl过期了,okhttp可能依然会去使用,这时候也是有风险的。

对比两种方案,各有各的优点,一个方便做容灾,但同时也暴露出了很多问题,一个不方便做容灾,但是之前暴露出来的问题都不存在。所以,这时候就得根据实际情况衡量选择哪一种方案了。

还有就是WebView的HttpDns,目前看来Android的Webview简直就是一个Bug的存在,没有什么好的解决方法。在IOS中,Webview的请求是一个正常的HttpRequest对象,不会像Android中存在这种问题,Andorid中比较好的解决方法就是在native层的网络库里开一个代理服务器,将Webview的所有请求转发至这个代理服务器,由这个代理服务器将请求通过httpdns转换成ip请求,将请求结果返回给webview。

或者通过WebView的资源拦截接口拦截资源请求,不过这种方式只能处理资源,处理正常的http/https请求会存在问题,在Android5.0以下存在cookie种不进去的问题。

总之WebView就是蛋疼的存在,没有什么好的办法,除非有自己的Webview的容器~

WebView安全

阅读数 380

没有更多推荐了,返回首页