关于linux 蓝牙工具bluez的资料太少又过时,只好自己摸索。。

安装blueman,blueman是一个基于gtk的蓝牙图形管理界面,用python写的

树莓派蓝牙局域网pan配置踩坑记

树莓派蓝牙局域网pan配置踩坑记

选择插件,安装pan相关插件

树莓派蓝牙局域网pan配置踩坑记

选择本地服务,选择dhcpd(3),Blueman(dhclient),Apply,Apply点不了的话改一下ip再改回来,可能会提示读取不到dhcpd.pan1.pid文件错误,确定不用管。使用前确保安装了dhcp服务端dhcpd和dhcp客户端dhcpcd

ps:这里dhcpd(3)本来在dnsmasq左边,我瞎改了源码,改到右边了。。

树莓派蓝牙局域网pan配置踩坑记

运行下面脚本启动NAP服务,此脚本是bluman的源文件/usr/lib/python3/dist-packages/blueman/main/NetConfig.py稍微改动过来的,命令为bluetooth_nap_startup.py

sudo python3 bluetooth_nap_startup.py

from __future__ import print_function
from __future__ import division
from __future__ import absolute_import
from __future__ import unicode_literals
from io import open

import pickle
import os
import signal
import errno
import re
from socket import inet_aton, inet_ntoa
from blueman.Constants import *
from blueman.Functions import have, mask_ip4_address, dprint, is_running
from _blueman import create_bridge, destroy_bridge, BridgeException
from subprocess import call, Popen
from socket import inet_aton, inet_ntoa


def calc_ip_range(ip):
    '''Calculate the ip range for dhcp config'''
    start_range = bytearray(ip)
    end_range = bytearray(ip)
    start_range[3] += 1
    end_range[3] = 254

    return bytes(start_range), bytes(end_range)

def read_pid_file(fname):
    try:
        with open(fname, "r") as f:
            pid = int(f.read())
    except (IOError, ValueError):
        pid = None

    return pid

class DnsMasqHandler(object):
    def __init__(self, netconf):
        self.pid = None
        self.netconf = netconf

    def do_apply(self):
        if not self.netconf.locked("dhcp") or self.netconf.ip4_changed:
            if self.netconf.ip4_changed:
                self.do_remove()

            if 1:
                rtr = "--dhcp-option=option:router,%s" % inet_ntoa(self.netconf.ip4_address)
            else:
                rtr = "--dhcp-option=3 --dhcp-option=6"  # no route and no dns

            start, end = calc_ip_range(self.netconf.ip4_address)

            args = "--pid-file=/var/run/dnsmasq.pan1.pid --bind-interfaces --dhcp-range=%s,%s,60m --except-interface=lo --interface=pan1 %s" % (inet_ntoa(start), inet_ntoa(end), rtr)

            cmd = [have("dnsmasq")] + args.split(" ")
            dprint(cmd)
            p = Popen(cmd)

            ret = p.wait()

            if ret == 0:
                dprint("Dnsmasq started correctly")
                f = open("/var/run/dnsmasq.pan1.pid", "r")
                self.pid = int(f.read())
                f.close()
                dprint("pid", self.pid)
                self.netconf.lock("dhcp")
            else:
                raise Exception("dnsmasq failed to start. Check the system log for errors")


    def do_remove(self):
        if self.netconf.locked("dhcp"):
            if not self.pid:
                pid = read_pid_file("/var/run/dnsmasq.pan1.pid")
            else:
                pid = self.pid

            running = is_running("dnsmasq", pid) if pid else False

            if running:
                os.kill(pid, signal.SIGTERM)
                self.netconf.unlock("dhcp")
            else:
                dprint("Stale dhcp lockfile found")
                self.netconf.unlock("dhcp")

class DhcpdHandler(object):
    def __init__(self, netconf):
        self.pid = None
        self.netconf = netconf

    def _read_dhcp_config(self):
        f = open(DHCP_CONFIG_FILE, "r")
        insection = False
        dhcp_config = ""
        existing_subnet = ""
        for line in f:
            if line == "#### BLUEMAN AUTOMAGIC SUBNET ####\n":
                insection = True

            if line == "#### END BLUEMAN AUTOMAGIC SUBNET ####\n":
                insection = False

            if not insection:
                dhcp_config += line
            else:
                existing_subnet += line

        f.close()

        return (dhcp_config, existing_subnet)

    def get_dns_servers(self):
        f = open("/etc/resolv.conf", "r")
        dns_servers = ""
        for line in f:
            server = re.search("^nameserver (.*)", line)
            if server:
                server = server.groups(1)[0]
                dns_servers += "%s, " % server;

        dns_servers = dns_servers.strip(", ")

        f.close()

        return dns_servers

    def _generate_subnet_config(self):
        dns = self.get_dns_servers()

        masked_ip = mask_ip4_address(self.netconf.ip4_address, self.netconf.ip4_mask)

        start, end = calc_ip_range(self.netconf.ip4_address)

        subnet = "#### BLUEMAN AUTOMAGIC SUBNET ####\n"
        subnet += "# Everything inside this section is destroyed after config change\n"
        subnet += """subnet %(ip_mask)s netmask %(netmask)s {
                option domain-name-servers %(dns)s;
                option subnet-mask %(netmask)s;
                option routers %(rtr)s;
                range %(start)s %(end)s;
                }\n""" % {"ip_mask": inet_ntoa(masked_ip),
                          "netmask": inet_ntoa(self.netconf.ip4_mask),
                          "dns": dns,
                          "rtr": inet_ntoa(self.netconf.ip4_address),
                          "start": inet_ntoa(start),
                          "end": inet_ntoa(end)
        }
        subnet += "#### END BLUEMAN AUTOMAGIC SUBNET ####\n"

        return subnet

    def do_apply(self):
        if not self.netconf.locked("dhcp") or self.netconf.ip4_changed:
            if self.netconf.ip4_changed:
                self.do_remove()

            dhcp_config, existing_subnet = self._read_dhcp_config()

            subnet = self._generate_subnet_config()

            # if subnet != self.existing_subnet:
            f = open(DHCP_CONFIG_FILE, "w")
            f.write(dhcp_config)
            f.write(subnet)
            f.close()

            os.mkdir("/var/run/dhcpd-server/")
            cmd = [have("dhcpd3") or have("dhcpd"), "-pf", "/var/run/dhcpd-server/dhcpd.pan1.pid", "pan1"]
            p = Popen(cmd)

            ret = p.wait()

            if ret == 0:
                dprint("dhcpd started correctly")
                f = open("/var/run/dhcpd-server/dhcpd.pan1.pid", "r")
                self.pid = int(f.read())
                f.close()
                dprint("pid", self.pid)
                self.netconf.lock("dhcp")
            else:
                raise Exception("dhcpd failed to start. Check the system log for errors")


    def do_remove(self):
        dhcp_config, existing_subnet = self._read_dhcp_config()
        f = open(DHCP_CONFIG_FILE, "w")
        f.write(dhcp_config)
        f.close()

        if self.netconf.locked("dhcp"):
            if not self.pid:
                pid = read_pid_file("/var/run/dhcpd-server/dhcpd.pan1.pid")
            else:
                pid = self.pid

            running = is_running("dnsmasq", pid) if pid else False

            if running:
                os.kill(pid, signal.SIGTERM)
                self.netconf.unlock("dhcp")
            else:
                dprint("Stale dhcp lockfile found")
                self.netconf.unlock("dhcp")

class_id = 10


class NetConf(object):
    default_inst = None

    @classmethod
    def get_default(cls):
        if NetConf.default_inst:
            return NetConf.default_inst

        try:
            f = open("/var/lib/blueman/network.state", "rb")
            obj = pickle.load(f)
            if obj.version != class_id:
                raise Exception
            NetConf.default_inst = obj
            f.close()
            return obj
        except IOError:
            n = cls()
            try:
                n.store()
            except:
                return n
            NetConf.default_inst = n
            return n

    def __init__(self):
        dprint()
        self.version = class_id
        self.dhcp_handler = None
        self.ipt_rules = []

        self.ip4_address = None
        self.ip4_mask = None
        self.ip4_changed = False


    def set_ipv4(self, ip, netmask):
        if self.ip4_address != ip or self.ip4_mask != netmask:
            self.ip4_changed = True

        self.ip4_address = ip
        self.ip4_mask = netmask

    def get_ipv4(self):
        return (self.ip4_address, self.ip4_mask)

    def enable_ip4_forwarding(self):
        f = open("/proc/sys/net/ipv4/ip_forward", "w")
        f.write("1")
        f.close()

        for d in os.listdir("/proc/sys/net/ipv4/conf"):
            f = open("/proc/sys/net/ipv4/conf/%s/forwarding" % d, "w")
            f.write("1")
            f.close()

    def add_ipt_rule(self, table, chain, rule):
        self.ipt_rules.append((table, chain, rule))
        args = ["/sbin/iptables", "-t", table, "-A", chain] + rule.split(" ")
        dprint(" ".join(args))
        ret = call(args)
        print("Return code", ret)

    def del_ipt_rules(self):
        for table, chain, rule in self.ipt_rules:
            call(["/sbin/iptables", "-t", table, "-D", chain] + rule.split(" "))
        self.ipt_rules = []
        self.unlock("iptables")

    def set_dhcp_handler(self, handler):
        if not isinstance(self.dhcp_handler, handler):
            running = False
            if self.dhcp_handler:
                self.dhcp_handler.do_remove()
                running = True

            self.dhcp_handler = handler(self)
            if running:
                self.dhcp_handler.do_apply()

    def get_dhcp_handler(self):
        if not self.dhcp_handler:
            return None

        return type(self.dhcp_handler)

    def apply_settings(self):
        if self.ip4_address == None or self.ip4_mask == None:
            if self.ip4_changed:
                self.ip4_changed = False
            self.store()
            return

        if self != NetConf.get_default():
            NetConf.get_default().remove_settings()
            NetConf.default_inst = self

        try:
            create_bridge("pan1")
        except BridgeException as e:
            if e.errno != errno.EEXIST:
                raise

        ip_str = inet_ntoa(self.ip4_address)
        mask_str = inet_ntoa(self.ip4_mask)

        if self.ip4_changed or not self.locked("ifconfig"):
            self.enable_ip4_forwarding()

            ret = call(["ifconfig", "pan1", ip_str, "netmask", mask_str, "up"])
            if ret != 0:
                raise Exception("Failed to setup interface pan1")
            self.lock("ifconfig")

        if self.ip4_changed or not self.locked("iptables"):
            self.del_ipt_rules()

            self.add_ipt_rule("nat", "POSTROUTING", "-s %s/%s -j MASQUERADE" % (ip_str, mask_str))
            self.add_ipt_rule("filter", "FORWARD", "-i pan1 -j ACCEPT")
            self.add_ipt_rule("filter", "FORWARD", "-o pan1 -j ACCEPT")
            self.add_ipt_rule("filter", "FORWARD", "-i pan1 -j ACCEPT")
            self.lock("iptables")

        if self.dhcp_handler:
            self.dhcp_handler.do_apply()
        self.ip4_changed = False

        self.store()

    def remove_settings(self):
        dprint(self)

        if self.dhcp_handler:
            self.dhcp_handler.do_remove()

        try:
            destroy_bridge("pan1")
        except:
            pass
        self.unlock("ifconfig")

        self.del_ipt_rules()

        self.store()

    def lock(self, key):
        f = open("/var/run/blueman-%s" % key, "w")
        f.close()

    def unlock(self, key):
        try:
            os.unlink("/var/run/blueman-%s" % key)
        except:
            pass

    def locked(self, key):
        return os.path.exists("/var/run/blueman-%s" % key)

    # save the instance of this class, requires root
    def store(self):
        if not os.path.exists("/var/lib/blueman"):
            os.mkdir("/var/lib/blueman")
        f = open("/var/lib/blueman/network.state", "wb")
        pickle.dump(self, f, 2)
        f.close()


if __name__ == "__main__":
    DHCPDHANDLERS = {"DnsMasqHandler": DnsMasqHandler,
                     "DhcpdHandler": DhcpdHandler}
    nc = NetConf.get_default()
    nc.set_ipv4(inet_aton("10.10.10.1"), inet_aton("255.255.255.0"))
    nc.set_dhcp_handler(DHCPDHANDLERS["DhcpdHandler"])
    nc.apply_settings()

运行成功后,运行ifconfig,显示多了一个pan1网卡

树莓派蓝牙局域网pan配置踩坑记

 这是就可以用蓝牙连接树莓派局域网,主要连接蓝牙要选择网络连接,连接成功后再ifconfig,又多了一个bnep0网卡

树莓派蓝牙局域网pan配置踩坑记

 重启系统,系统启动后可能会提示python异常

树莓派蓝牙局域网pan配置踩坑记

 确定不用管

修改blueman源码文件/usr/lib/blueman/blueman-mechanism,添加导入模块

from blueman.main.NetConf import NetConf, DnsMasqHandler, DhcpdHandler

修改blueman源码文件/usr/lib/python3/dist-packages/blueman/main/NetConf.py,DhcpdHandler类的do_apply方法

树莓派蓝牙局域网pan配置踩坑记

之前的异常就是上面修改的几个地方文件路径不一致造成的,这么明显的bug竟然没有发现。。。

改好之后重启系统,应该不会再出现错误提示,而且开机之后NAP服务自动启动了,可以直接通过蓝牙网络连接树莓派。

树莓派的ip固定为之前 bluetooth_nap_startup.py文件中的"10.10.10.1",根据自己需求改。

blueman会在NAP服务停止后会将配置状态序列化为文件存储起来,相关代码:

/usr/lib/python3/dist-packages/blueman/main/NetConf.py 中的 NetConf类

树莓派蓝牙局域网pan配置踩坑记

NetConf类初始化时会加载之前序列化的文件,然后反序列化

树莓派蓝牙局域网pan配置踩坑记

 之前开机启动提示的错误就是没有导入DhcpdHanler导致反序列化失败。

相关文章: