Skip to content

Instantly share code, notes, and snippets.

@abbeycode
Last active January 18, 2019 06:39
Show Gist options
  • Save abbeycode/b97801aa424495215f93 to your computer and use it in GitHub Desktop.
Save abbeycode/b97801aa424495215f93 to your computer and use it in GitHub Desktop.
A class to facilitate publishing a Bonjour service in Swift
//
// BonjourPublisher.swift
// BonjourTest
//
// Created by Dov Frankel on 10/12/15.
// Copyright © 2015 Dov Frankel. All rights reserved.
//
import Foundation
public enum BonjourServerError: ErrorType {
case SettingIPv6Options
case BindingIPv4
case BindingIPv6
case GettingSocketIPv4
case GettingSocketIPv6
case ListeningIPv4
case ListeningIPv6
case CreatingNativeSocket
case CreatingSocketRunLoopSource
case PublishingService([String : NSNumber])
}
@objc
public class BonjourPublisher: NSObject, NSNetServiceDelegate {
public let serviceType: String
public let serviceName: String
private var service: NSNetService!
private var serviceSocket: CFSocket!
private var serviceRunLoopSource: CFRunLoopSource!
private var publishCompletedCallback: ((success: Bool, error: BonjourServerError?) -> Void)!
init(serviceType: String, serviceName: String) {
self.serviceType = serviceType
self.serviceName = serviceName
}
convenience init(serviceType: String) {
self.init(serviceType: serviceType, serviceName: "")
}
public func configure(completionBlock: (success: Bool, error: BonjourServerError?) -> Void) {
dispatch_async(dispatch_get_main_queue()) {
let fd4, fd6: Int32
let port: in_port_t
do {
(fd4, port) = try self.registerIPv4Socket()
fd6 = try self.registerIPv6Socket(port)
} catch let socketError {
NSLog("Failed to register a socket: BonjourServerError.\(socketError)")
completionBlock(success: false, error: socketError as? BonjourServerError)
return
}
do {
try self.hookUpSocket(fd4)
} catch let socketError {
NSLog("Failed to hoook up an IPv4 socket: BonjourServerError.\(socketError)")
completionBlock(success: false, error: socketError as? BonjourServerError)
return
}
do {
try self.hookUpSocket(fd6)
} catch let socketError {
NSLog("Failed to hoook up an IPv6 socket: BonjourServerError.\(socketError)")
completionBlock(success: false, error: socketError as? BonjourServerError)
return
}
self.publishCompletedCallback = completionBlock
self.service = self.publishService(port)
}
}
private func registerIPv4Socket() throws -> (Int32, in_port_t) {
let fd4 = socket(AF_INET, SOCK_STREAM, 0)
var sin = sockaddr_in()
sin.sin_family = sa_family_t(AF_INET)
sin.sin_len = UInt8(sizeofValue(sin))
sin.sin_port = 0
let bindError = withUnsafePointer(&sin) {
Foundation.bind(fd4, UnsafePointer($0), UInt32(sin.sin_len))
}
guard bindError == 0 else {
throw BonjourServerError.BindingIPv4
}
var addrLen = socklen_t(sizeofValue(sin))
let socketError = withUnsafeMutablePointers(&sin, &addrLen) { (sinPtr, addrPtr) -> Int32 in
getsockname(fd4, UnsafeMutablePointer(sinPtr), UnsafeMutablePointer(addrPtr))
}
guard socketError == 0 else {
throw BonjourServerError.GettingSocketIPv4
}
let listenError = listen(fd4, 5)
guard listenError == 0 else {
throw BonjourServerError.ListeningIPv4
}
return (fd4, sin.sin_port)
}
private func registerIPv6Socket(port: in_port_t) throws -> Int32 {
let fd6 = socket(AF_INET6, SOCK_STREAM, 0)
var one: Int32 = 1
let sockOptError = withUnsafePointer(&one) {
setsockopt(fd6, IPPROTO_IPV6, IPV6_V6ONLY, UnsafePointer($0), socklen_t(sizeofValue(one)))
}
guard sockOptError == 0 else {
throw BonjourServerError.SettingIPv6Options
}
var sin6 = sockaddr_in6()
sin6.sin6_family = sa_family_t(AF_INET6)
sin6.sin6_len = UInt8(sizeofValue(sin6))
sin6.sin6_port = port.bigEndian
let bindError = withUnsafePointer(&sin6) {
Foundation.bind(fd6, UnsafePointer($0), UInt32(sin6.sin6_len))
}
guard bindError == 0 else {
throw BonjourServerError.BindingIPv6
}
var addrLen = socklen_t(sizeofValue(sin6))
let socketError = withUnsafeMutablePointers(&sin6, &addrLen) { (sinPtr, addrPtr) -> Int32 in
getsockname(fd6, UnsafeMutablePointer(sinPtr), UnsafeMutablePointer(addrPtr))
}
guard socketError == 0 else {
throw BonjourServerError.GettingSocketIPv6
}
let listenError = listen(fd6, 5)
guard listenError == 0 else {
throw BonjourServerError.ListeningIPv6
}
return fd6
}
private func hookUpSocket(fd: CFSocketNativeHandle) throws {
var context = CFSocketContext()
var selfPtr = self
withUnsafeMutablePointer(&selfPtr) {
context.info = UnsafeMutablePointer<Void>($0)
}
serviceSocket = withUnsafePointer(&context) {
CFSocketCreateWithNative(nil, fd, CFSocketCallBackType.AcceptCallBack.rawValue, CallbackListen, UnsafePointer<CFSocketContext>($0))
}
guard serviceSocket != nil && CFSocketIsValid(serviceSocket) else {
throw BonjourServerError.CreatingNativeSocket
}
serviceRunLoopSource = CFSocketCreateRunLoopSource(nil, serviceSocket, 0)
guard serviceRunLoopSource != nil && CFRunLoopSourceIsValid(serviceRunLoopSource) else {
throw BonjourServerError.CreatingSocketRunLoopSource
}
CFRunLoopAddSource(CFRunLoopGetCurrent(), serviceRunLoopSource, kCFRunLoopCommonModes)
}
private func publishService(port: in_port_t) -> NSNetService {
let serviceTypeAndTransport = "_\(self.serviceType)._tcp."
let service = NSNetService(domain: "", type: serviceTypeAndTransport, name: serviceName, port: Int32(port.bigEndian))
service.delegate = self
service.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSRunLoopCommonModes)
service.publish()
return service
}
// MARK: - Callbacks
@objc public func netServiceDidPublish(sender: NSNetService) {
NSLog("Advertising service with name '\(sender.name)' on port \(sender.port)")
guard publishCompletedCallback != nil else {
NSLog("Publish succeeded, but there was no callback reference")
return
}
publishCompletedCallback(success: true, error: nil)
self.publishCompletedCallback = nil
}
@objc public func netService(sender: NSNetService, didNotPublish errorDict: [String : NSNumber]) {
NSLog("Error publishing: \(errorDict)")
guard publishCompletedCallback != nil else {
NSLog("Publish failed, but there was no callback reference")
return
}
publishCompletedCallback(success: false, error: BonjourServerError.PublishingService(errorDict))
self.publishCompletedCallback = nil
}
}
func CallbackListen(s: CFSocket!, callbackType: CFSocketCallBackType, address: CFData!, data: UnsafePointer<Void>, info: UnsafeMutablePointer<Void>) {
NSLog("Callback called")
}
@abbeycode
Copy link
Author

Updated to fix bug in Stack Overflow question.

@pablogeek
Copy link

how can I make this work for peer to peer connections? seems this doesn't like ipv6.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment