android—给app指定网络接口原理分析


android支持多种网络类型(WAN口),例如WIFI、3G等。目前android的实现是,WIFI和3G只能同时存在一个(优先级),例如当WIFI连接后,数据通路就从3G切换到WIFI。对上层app而言,这时候数据通路也就从3G切换到WIFI上。

考虑一个特殊的需求,某app只能通过WIFI接口去传输数据,是否可以实现?较新版本的android已经支持了该功能,通过调用setProcessDefaultNetwork()可以指定某一进程的网络接口,

        /**
     * Binds the current process to {@code network}.  All Sockets created in the future
     * (and not explicitly bound via a bound SocketFactory from
     * {@link Network#getSocketFactory() Network.getSocketFactory()}) will be bound to
     * {@code network}.  All host name resolutions will be limited to {@code network} as well.
     * Note that if {@code network} ever disconnects, all Sockets created in this way will cease to
     * work and all host name resolutions will fail.  This is by design so an application doesn't
     * accidentally use Sockets it thinks are still bound to a particular {@link Network}.
     * To clear binding pass {@code null} for {@code network}.  Using individually bound
     * Sockets created by Network.getSocketFactory().createSocket() and
     * performing network-specific host name resolutions via
     * {@link Network#getAllByName Network.getAllByName} is preferred to calling
     * {@code setProcessDefaultNetwork}.
     *
     * @param network The {@link Network} to bind the current process to, or {@code null} to clear
     *                the current binding.
     * @return {@code true} on success, {@code false} if the {@link Network} is no longer valid.
     */
    public static boolean setProcessDefaultNetwork(Network network) {

    }

该函数的实现原理大致为,
1. 该进程在创建socket时(app首先调用setProcessDefaultNetwork()),android底层会利用setsockopt函数设置该socket的SO_MARK为netId(android有自己的管理逻辑,每个Network有对应的ID),以后利用该socket发送的数据都会被打上netId的标记(fwmark 值)。
2. 利用策略路由,将打着netId标记的数据包都路由到WIFI的接口wlan0。
这里先介绍打标签的原理,至于策略路由的创建,后续再分析,下面是策略路由表的一个简单例子。

    shell@msm8916_64:/ $ ip rule list
    ip rule list
    0:      from all lookup local
    10000:  from all fwmark 0xc0000/0xd0000 lookup 99
    13000:  from all fwmark 0x10063/0x1ffff lookup 97
    13000:  from all fwmark 0x10064/0x1ffff lookup 1012
    14000:  from all oif rmnet_data0 lookup 1012
    15000:  from all fwmark 0x0/0x10000 lookup 99
    16000:  from all fwmark 0x0/0x10000 lookup 98
    17000:  from all fwmark 0x0/0x10000 lookup 97
    19000:  from all fwmark 0x64/0x1ffff lookup 1012
    22000:  from all fwmark 0x0/0xffff lookup 1012
    23000:  from all fwmark 0x0/0xffff uidrange 0-0 lookup main
    32000:  from all unreachable
    shell@msm8916_64:/ $

java层利用JNI和C库进行交互

android java库是libcore\目录下的相关代码,而上层的java api基本都是调用该java库中的实现,然后java库通过JNI的方式调用底层的实现,例如android中的c库bionic\等。

下面,我们从java层调用Socket函数开始,分析上述调用的过程。



     public Socket(String dstName, int dstPort, InetAddress localAddress, int localPort) throws IOException {
        //无参构造函数
        this();
        tryAllAddresses(dstName, dstPort, localAddress, localPort, true);
    }
        //Socket的实现是PlainSocketImpl
    public Socket() {
        this.impl = factory != null ? factory.createSocketImpl() : new PlainSocketImpl();
        this.proxy = null;
    }

    private void tryAllAddresses(String dstName, int dstPort, InetAddress
            localAddress, int localPort, boolean streaming) throws IOException {

        startupSocket(dstAddress, dstPort, localAddress, localPort, streaming);
 }



  private void startupSocket(InetAddress dstAddress, int dstPort,
            InetAddress localAddress, int localPort, boolean streaming)
            throws IOException {
        synchronized (this) {
            //其实是调用impl的create和connect函数,
            //impl即是PlainSocketImpl
            impl.create(streaming);
            impl.connect(dstAddress, dstPort);
    }
    //-------libcore\luni\src\main\java\java\net\PlainSocketImpl.java

        protected void create(boolean streaming) throws IOException {
        this.streaming = streaming;
        //调用IoBridge的socket函数
        this.fd = IoBridge.socket(streaming);
    }
    //-------libcore\luni\src\main\java\libcore\io\IoBridge.java

      public static FileDescriptor socket(boolean stream) throws SocketException {
        FileDescriptor fd;
        try {
            //调用类Libcore静态成员os的socket方法
            fd = Libcore.os.socket(AF_INET6, stream ? SOCK_STREAM : SOCK_DGRAM, 0);
            return fd;
        } catch (ErrnoException errnoException) {
            throw errnoException.rethrowAsSocketException();
        }
    }
package libcore.io;

public final class Libcore {

    private Libcore() { }

    public static Os os = new BlockGuardOs(new Posix());
}

// new Posix()

public interface Os {

}

其实最终调用的是类Posix中的方法,下面是Posix中的socket()函数,是个native函数,

// libcore\luni\src\main\java\libcore\io\Posix.java
public native FileDescriptor socket(int domain, int type, int protocol) throws ErrnoException;

其实现为,

// libcore\luni\src\main\native\libcore_io_Posix.cpp 
static jobject Posix_socket(JNIEnv* env, jobject, jint domain, jint type, jint protocol) {
    int fd = throwIfMinusOne(env, "socket", TEMP_FAILURE_RETRY(socket(domain, type, protocol)));
    return fd != -1 ? jniCreateFileDescriptor(env, fd) : NULL;
}

//进而调用C库中的函数
// -------------->bionic\libc\bionic\socket.cpp
int socket(int domain, int type, int protocol) {
    return __netdClientDispatch.socket(domain, type, protocol);
}

bonic库中socket相关的实现

在bonic库中,__netdClientDispatch是什么?

// bionic\libc\private\NetdClientDispatch.h

//NetdClientDispatch 结构体包含了socket、accept4、connect、netIdForResolv
//这几个函数的指针
struct NetdClientDispatch {
    int (*accept4)(int, struct sockaddr*, socklen_t*, int);
    int (*connect)(int, const struct sockaddr*, socklen_t);
    int (*socket)(int, int, int);
    unsigned (*netIdForResolv)(unsigned);
};
// __netdClientDispatch为NetdClientDispatch的"实例"
extern __LIBC_HIDDEN__ struct NetdClientDispatch __netdClientDispatch;

//bionic\libc\bionic\NetdClientDispatch.cpp

extern "C" __socketcall int __accept4(int, sockaddr*, socklen_t*, int);
extern "C" __socketcall int __connect(int, const sockaddr*, socklen_t);
extern "C" __socketcall int __socket(int, int, int);

static unsigned fallBackNetIdForResolv(unsigned netId) {
    return netId;
}

// 填充__netdClientDispatch成员,都是类似标准C库中的函数实现,利用系统调用
// 和操作系统交互
// This structure is modified only at startup (when libc.so is loaded) and never
// afterwards, so it's okay that it's read later at runtime without a lock.
__LIBC_HIDDEN__ NetdClientDispatch __netdClientDispatch __attribute__((aligned(32))) = {
    __accept4,
    __connect,
    __socket,
    fallBackNetIdForResolv,
};

到此,我们分析了__netdClientDispatch是什么。

此外,bonic在preinit时会调用netdClientInit(),分析下发生了什么,

/*----bionic\libc\bionic\libc_init_dynamic.cpp----*/

__attribute__((constructor)) static void __libc_preinit() {
  //bonic在preinit时会调用netdClientInit()
  netdClientInit();
}
/*----bionic\libc\bionic\NetdClient.cpp----*/

extern "C" __LIBC_HIDDEN__ void netdClientInit() {
    if (pthread_once(&netdClientInitOnce, netdClientInitImpl)) {
        __libc_format_log(ANDROID_LOG_ERROR, "netdClient", "Failed to initialize netd_client");
    }
/*----bionic\libc\bionic\NetdClient.cpp----*/

static void netdClientInitImpl() {
    //打开库libnetd_client.so,查找netdClientInitSocket、
    //netdClientInitAccept4等这几个函数的实现
    void* netdClientHandle = dlopen("libnetd_client.so", RTLD_LAZY);
    if (netdClientHandle == NULL) {
        // If the library is not available, it's not an error. We'll just use
        // default implementations of functions that it would've overridden.
        return;
    }
    netdClientInitFunction(netdClientHandle, "netdClientInitAccept4",
                           &__netdClientDispatch.accept4);
    netdClientInitFunction(netdClientHandle, "netdClientInitConnect",
                           &__netdClientDispatch.connect);
    netdClientInitFunction(netdClientHandle, "netdClientInitNetIdForResolv",
                           &__netdClientDispatch.netIdForResolv);
    netdClientInitFunction(netdClientHandle, "netdClientInitSocket", &__netdClientDispatch.socket);
}

//在handle中,即库中找symbol
//以netdClientInitSocket为例,传入的function为&__netdClientDispatch.socket,
//函数的指针,
template <typename FunctionType>
static void netdClientInitFunction(void* handle, const char* symbol, FunctionType* function) {
    typedef void (*InitFunctionType)(FunctionType*);
    InitFunctionType initFunction = reinterpret_cast<InitFunctionType>(dlsym(handle, symbol));
    if (initFunction != NULL) {
        initFunction(function);
    }
}

而库libnetd_client.so是在netd中,system\netd\client\

typedef int (*SocketFunctionType)(int, int, int);

SocketFunctionType libcSocket = 0;

extern "C" void netdClientInitSocket(SocketFunctionType* function) {
    //这里*function在前面已经赋值,就是标准c库中的socket函数
    //libcSocket 赋值为__socket
    //而将__netdClientDispatch.socket赋值为netdClientSocket
    if (function && *function) {
        libcSocket = *function;
        *function = netdClientSocket;
    }
}

所以在bonic在初始化时,主要做的工作就是将__netdClientDispatch.socket函数设置为netdClientSocket(),而libcSocket 赋值为__socket()(socket相关函数的实现都类似)。

setProcessDefaultNetwork()的实现

    public static boolean setProcessDefaultNetwork(Network network) {
        int netId = (network == null) ? NETID_UNSET : network.netId;
        if (netId == NetworkUtils.getNetworkBoundToProcess()) {
            return true;
        }
        //调用bindProcessToNetwork()
        if (NetworkUtils.bindProcessToNetwork(netId)) {
            return true;
        } else {
            return false;
        }
    }

bindProcessToNetwork()利用JNI实现,

    /**
     * Binds the current process to the network designated by {@code netId}.  All sockets created
     * in the future (and not explicitly bound via a bound {@link SocketFactory} (see
     * {@link Network#getSocketFactory}) will be bound to this network.  Note that if this
     * {@code Network} ever disconnects all sockets created in this way will cease to work.  This
     * is by design so an application doesn't accidentally use sockets it thinks are still bound to
     * a particular {@code Network}.  Passing NETID_UNSET clears the binding.
     */
    public native static boolean bindProcessToNetwork(int netId);
static jboolean android_net_utils_bindProcessToNetwork(JNIEnv *env, jobject thiz, jint netId)
{
    return (jboolean) !setNetworkForProcess(netId);
}
//进程的netId,netIdForProcess
std::atomic_uint netIdForProcess(NETID_UNSET);

extern "C" int setNetworkForProcess(unsigned netId) {
    return setNetworkForTarget(netId, &netIdForProcess);
}
int setNetworkForTarget(unsigned netId, std::atomic_uint* target) {
    if (netId == NETID_UNSET) {
        *target = netId;
        return 0;
    }
    // Verify that we are allowed to use |netId|, by creating a socket and trying to have it marked
    // with the netId. Call libcSocket() directly; else the socket creation (via netdClientSocket())
    // might itself cause another check with the fwmark server, which would be wasteful.
    int socketFd;
    //libcSocket 赋值为__socket
    if (libcSocket) {
        socketFd = libcSocket(AF_INET6, SOCK_DGRAM, 0);
    } else {
        socketFd = socket(AF_INET6, SOCK_DGRAM, 0);
    }
    if (socketFd < 0) {
        return -errno;
    }
    int error = setNetworkForSocket(netId, socketFd);
    if (!error) {
        //将netIdForProcess设置为netId
        *target = netId;
    }
    close(socketFd);
    return error;
}
        //给socketFd这个socket设置网络ID
        extern "C" int setNetworkForSocket(unsigned netId, int socketFd) {
        if (socketFd < 0) {
            return -EBADF;
        }
        //要发送的是FwmarkCommand结构体,命令为SELECT_NETWORK
        FwmarkCommand command = {FwmarkCommand::SELECT_NETWORK, netId, 0};
        return FwmarkClient().send(&command, sizeof(command), socketFd);
    }
//上层创建的socket为fd,而需要设置的netid在data中
    int FwmarkClient::send(void* data, size_t len, int fd) {
    mChannel = socket(AF_UNIX, SOCK_STREAM, 0);
    if (mChannel == -1) {
        return -errno;
    }

    //这里连接fwserver,向server发东西
    //fwserver即/dev/socket/fwmarkd,init.rc中在netd启动时创建的socket
    if (TEMP_FAILURE_RETRY(connect(mChannel, reinterpret_cast<const sockaddr*>(&FWMARK_SERVER_PATH),
                                   sizeof(FWMARK_SERVER_PATH))) == -1) {
        // If we are unable to connect to the fwmark server, assume there's no error. This protects
        // against future changes if the fwmark server goes away.
        return 0;
    }

    iovec iov;
    iov.iov_base = data;
    iov.iov_len = len;

    msghdr message;
    memset(&message, 0, sizeof(message));
    message.msg_iov = &iov;
    message.msg_iovlen = 1;

    union {
        cmsghdr cmh;
        char cmsg[CMSG_SPACE(sizeof(fd))];
    } cmsgu;

    memset(cmsgu.cmsg, 0, sizeof(cmsgu.cmsg));
    message.msg_control = cmsgu.cmsg;
    message.msg_controllen = sizeof(cmsgu.cmsg);

    //把上层创建的socket,即需要设置netId的socket放到这里
    cmsghdr* const cmsgh = CMSG_FIRSTHDR(&message);
    cmsgh->cmsg_len = CMSG_LEN(sizeof(fd));
    cmsgh->cmsg_level = SOL_SOCKET;
    cmsgh->cmsg_type = SCM_RIGHTS;
    memcpy(CMSG_DATA(cmsgh), &fd, sizeof(fd));

    if (TEMP_FAILURE_RETRY(sendmsg(mChannel, &message, 0)) == -1) {
        return -errno;
    }

    int error = 0;

    if (TEMP_FAILURE_RETRY(recv(mChannel, &error, sizeof(error), 0)) == -1) {
        return -errno;
    }

    return error;
}

netd中的fwmarkd收到数据,

    //fwmarkd这个socket接收到数据啦
    bool FwmarkServer::onDataAvailable(SocketClient* client) {
    int socketFd = -1;
    int error = processClient(client, &socketFd);
    if (socketFd >= 0) {
        close(socketFd);
    }

    return false;
}
int FwmarkServer::processClient(SocketClient* client, int* socketFd) {
    FwmarkCommand command;

    iovec iov;
    iov.iov_base = &command;
    iov.iov_len = sizeof(command);

    msghdr message;
    memset(&message, 0, sizeof(message));
    message.msg_iov = &iov;
    message.msg_iovlen = 1;

    union {
        cmsghdr cmh;
        char cmsg[CMSG_SPACE(sizeof(*socketFd))];
    } cmsgu;

    memset(cmsgu.cmsg, 0, sizeof(cmsgu.cmsg));
    message.msg_control = cmsgu.cmsg;
    message.msg_controllen = sizeof(cmsgu.cmsg);

    //收取的数据中包含command,FwmarkCommand command = {FwmarkCommand::SELECT_NETWORK, netId, 0};
    //里面有需要设置的netId
    int messageLength = TEMP_FAILURE_RETRY(recvmsg(client->getSocket(), &message, 0));
    if (messageLength <= 0) {
        return -errno;
    }

    if (messageLength != sizeof(command)) {
        return -EBADMSG;
    }

    cmsghdr* const cmsgh = CMSG_FIRSTHDR(&message);
    //将client传送过来的需要设置NetId的socket赋值给socketFd,
    if (cmsgh && cmsgh->cmsg_level == SOL_SOCKET && cmsgh->cmsg_type == SCM_RIGHTS &&
        cmsgh->cmsg_len == CMSG_LEN(sizeof(*socketFd))) {
        memcpy(socketFd, CMSG_DATA(cmsgh), sizeof(*socketFd));
    }

    if (*socketFd < 0) {
        return -EBADF;
    }

    Fwmark fwmark;
    socklen_t fwmarkLen = sizeof(fwmark.intValue);
    if (getsockopt(*socketFd, SOL_SOCKET, SO_MARK, &fwmark.intValue, &fwmarkLen) == -1) {
        return -errno;
    }

    Permission permission = mNetworkController->getPermissionForUser(client->getUid());

    switch (command.cmdId) {

        case FwmarkCommand::SELECT_NETWORK: {
            fwmark.netId = command.netId;
            if (command.netId == NETID_UNSET) {
                fwmark.explicitlySelected = false;
                fwmark.protectedFromVpn = false;
                permission = PERMISSION_NONE;
            } else {
                if (int ret = mNetworkController->checkUserNetworkAccess(client->getUid(),
                                                                         command.netId)) {
                    return ret;
                }
                //设置explicitlySelected 为true
                fwmark.explicitlySelected = true;
                fwmark.protectedFromVpn = mNetworkController->canProtect(client->getUid());
            }
            break;
        }


    fwmark.permission = permission;
    //fwmark是个UNION,这里将NetId,给上层创立的socket即 socketfd设置SO_MARK为NetId数值
    if (setsockopt(*socketFd, SOL_SOCKET, SO_MARK, &fwmark.intValue,
                   sizeof(fwmark.intValue)) == -1) {
        return -errno;
    }

    return 0;
}

通过上面分析,setProcessDefaultNetwork()函数仅仅是将进程的netIdForProcess设置为netId。在调用setProcessDefaultNetwork()后,再继续创建socket,connect会做什么?

上面分析在java层调用Socket函数时,最终会调用__netdClientDispatch.socket(),而__netdClientDispatch.socket()被赋值为netdClientSocket,在此步骤中会取出setProcessDefaultNetwork()函数设置的netIdForProcess值,然后设置给socket为其fwmark。

int socket(int domain, int type, int protocol) {
    return __netdClientDispatch.socket(domain, type, protocol);
}
int netdClientSocket(int domain, int type, int protocol) {
    int socketFd = -1;
    if (propSocket) {
      socketFd = propSocket(domain, type, protocol);
    } else if (libcSocket) {
     //用标准C库函数创建socket
      socketFd = libcSocket(domain, type, protocol);
    }

    if (-1 == socketFd) {
      return -1;
    }

    //通过setProcessDefaultNetwork()已经将netIdForProcess设置为netId了,
    unsigned netId = netIdForProcess;

    if (netId != NETID_UNSET && FwmarkClient::shouldSetFwmark(domain)) {
        if (int error = setNetworkForSocket(netId, socketFd)) {
            return closeFdAndSetErrno(socketFd, error);
        }
    }

    return socketFd;
}

关于setNetworkForSocket()实现,上面已经分析过,主要就是为该socketFd通过setsockopt()设置socket的SO_MARK为netId,关于connect(),accept()的实现和socket()类似。

  • 7
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值