Linux下UDP广播java.net.SocketException: Permission denied

405 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第18天,点击查看活动详情

0. 背景

  首先交代环境:

OpenJDK8
CentOS7

  其次说一下,UDP广播发送消息是很常见的,今天在Linux环境下部署的时候,发送消息时,发生了如下的异常:

java.net.SocketException: 权限不够
        at sun.nio.ch.DatagramChannelImpl.send0(Native Method)
        at sun.nio.ch.DatagramChannelImpl.sendFromNativeBuffer(DatagramChannelImpl.java:528)
        at sun.nio.ch.DatagramChannelImpl.send(DatagramChannelImpl.java:505)
        at sun.nio.ch.DatagramChannelImpl.send(DatagramChannelImpl.java:469)

  如果按照问题描述检索类似问题,你大概率会搜索到,因为你监听了1024以内的端口导致,但是我监听到是10568。我甚至参照centOS 权限问题-selinux小结把SELinux关了,各种赋权限。在筋疲力竭之后,我开始思考:如果真的是代码问题呢?

1.切换搜索关键词

  我开始尝试检索sun.nio.ch.DatagramChannelImpl.send0 permission,发现了这样一篇问答:SecurityException在OS X上从Java发送广播。里面遇到了类似的问题,有一个回复是这样的:

做了一点谷歌搜索,你需要告诉套接字数据报通道正在使用它是一个使用代码的广播频道: channel.socket().setBroadcast(true); 我认为只是您需要在“通道”上设置广播套接字选项,这是底层的O / S套接字。 显然,一旦java7出现,这将在通道级别实现,但是当前您需要访问DatagramSocket来设置参数。

  是的,就是这一行配置:

channel.socket().setBroadcast(true);

  在Linux或者Mac下,加上它,就允许你广播了。至此,问题解决了。

2.socket.setBroadcast()是什么?

  socket.setBroadcast()方法是dgram模块中Socket类的内置应用程序编程接口,用于设置或清除SO_BROADCAST套接字选项,该选项可帮助客户端将数据报发送到特定的广播地址。

  SO_BROADCAST的原文解释如下:

Sets SO_BROADCAST for a socket. This option enables and disables the ability of the process to send broadcast messages. It is supported for only datagram sockets and only on networks that support the concept of a broadcast message (e.g. Ethernet, token ring, etc.),and it is set by default for DatagramSockets.

  我们看到,源码里设置它就是用truefalse。这个在DatagramSockets下默认是关闭的。

  那我们怎么就惹到它了呢?

  我们回到send这个函数,它在 sun.nio.ch.DatagramChannelImpl 里有一个检查,是检查是否为多播(Multicast)。

SecurityManager var7 = System.getSecurityManager();
if (var7 != null) {
    if (var5.isMulticastAddress()) {
        var7.checkMulticast(var5);
    } else {
        var7.checkConnect(var5.getHostAddress(), var4.getPort());
    }
}

  这个isMulticastAddress()非常生猛,直接判的就是你地址的前四位是不是1110,很显然,我是广播,不是多播。我就进了checkConnect,然后它进行了 checkPermission

  这个函数的官方解释是:

    /**
     * Throws a <code>SecurityException</code> if the requested
     * access, specified by the given permission, is not permitted based
     * on the security policy currently in effect.
     * <p>
     * This method calls <code>AccessController.checkPermission</code>
     * with the given permission.
     *
     * @param     perm   the requested permission.
     * @exception SecurityException if access is not permitted based on
     *            the current security policy.
     * @exception NullPointerException if the permission argument is
     *            <code>null</code>.
     * @since     1.2
     */
    public void checkPermission(Permission perm) {
        java.security.AccessController.checkPermission(perm);
    }

  意思就是这个就按照当前的安全策略来了,再往里走就是native方法了,懒得看了。反正它会检查io操作是否安全,应该是socket.setBroadcast()后可以帮助它去认定我们是合法的,有时间了再翻源码。