Mac OS VPN Connection Programmatically - objective-c

I want to create code which will create new connection with L2TP Protocol .
below is my code.
I didn't find any error but code run successfully and create new network but it's not showing data which i passes e.g username.
- (IBAction)connectVPN_Cliced:(NSButton *)sender{
VPNServiceConfig *config = [[VPNServiceConfig alloc]init];
config.username = #"username";
config.password = #"password";
config.endpointPrefix = #"Server IP end prefix";
config.endpointSuffix = #"Server IP end endpointSuffix";
config.sharedSecret = #"sharedkey";
config.name = #"L2TP By Mac Application";
config.endpoint = #"Server IP";
config.type = 1;
[self setupVPN:config];
}
- (AuthorizationRef) getAuth {
AuthorizationRef auth = NULL;
OSStatus status;
status = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, [self flags], &auth);
if (status == errAuthorizationSuccess) {
NSLog(#"Successfully obtained Authorization reference");
} else {
NSLog(#"Could not obtain Authorization reference");
exit(101);
}
return auth;
}
- (AuthorizationFlags) flags {
return kAuthorizationFlagDefaults |
kAuthorizationFlagExtendRights |
kAuthorizationFlagInteractionAllowed |
kAuthorizationFlagPreAuthorize;
}
- (void)setupVPN:(VPNServiceConfig *)vpnConfig {
// Obtaining permission to modify network settings
AuthorizationRef auth = [self getAuth];
SCPreferencesRef prefs = SCPreferencesCreateWithAuthorization(kCFAllocatorDefault, CFSTR("macVPN"), NULL, auth);
SCPreferencesUnlock(prefs);
//-------- This is i tried by me to add data but it's not working ------ Start //
SCPreferencesSetValue(prefs, CFSTR("Type"), CFSTR("PPP"));
SCPreferencesSetValue(prefs, CFSTR("SubType"), CFSTR("L2TP"));
SCPreferencesSetValue(prefs, CFSTR("AuthName"), CFSTR("username"));
SCPreferencesSetValue(prefs, CFSTR("AuthPassword"), CFSTR("password"));
SCPreferencesSetValue(prefs, CFSTR("CommRemoteAddress"), CFSTR("server ip"));
NSData *pppData = [#"0000" dataUsingEncoding:NSUTF8StringEncoding];
CFDataRef ref = CFDataCreate(kCFAllocatorDefault, pppData.bytes, pppData.length);
// CFPropertyListFormat
CFPropertyListRef propertyRef = CFPropertyListCreateWithData(kCFAllocatorDefault, ref, kCFPropertyListMutableContainers, nil,nil);
Boolean isValidData = CFPropertyListIsValid(propertyRef, kCFPropertyListXMLFormat_v1_0);
SCPreferencesSetValue(prefs, CFSTR("AuthPassword"), propertyRef);
//-------- This is i tried by me to add data but it's not working ------ End //
// Making sure other process cannot make configuration modifications
// by obtaining a system-wide lock over the system preferences.
if (SCPreferencesLock(prefs, TRUE)) {
NSLog(#"Gained superhuman rights.");
} else {
NSLog(#"Sorry, without superuser privileges I won't be able to add any VPN interfaces.");
return;
}
// If everything will work out fin
[self createService:vpnConfig usingPreferencesRef:prefs];
// We're done, other processes may modify the system configuration again
return;
}
- (void) createService:(VPNServiceConfig *)config usingPreferencesRef:(SCPreferencesRef)prefs {
NSLog(#"Creating new %# Service using %#", config.humanType, config);
// These variables will hold references to our new interfaces
SCNetworkInterfaceRef bottomInterface = SCNetworkInterfaceCreateWithInterface(kSCNetworkInterfaceIPv4,kSCNetworkInterfaceTypeL2TP);
SCNetworkInterfaceRef topInterface = SCNetworkInterfaceCreateWithInterface(bottomInterface, kSCNetworkInterfaceTypePPP);
// Creating a new, fresh VPN service in memory using the interface we already created
SCNetworkServiceRef service = SCNetworkServiceCreate(prefs, topInterface);
// That service is to have a name
SCNetworkServiceSetName(service, (__bridge CFStringRef)config.name);
// And we also woould like to know the internal ID of this service
NSString *serviceID = (__bridge NSString *)(SCNetworkServiceGetServiceID(service));
// It will be used to find the correct passwords in the system keychain
//config.serviceID = serviceID;
[config setServiceID:serviceID];
// Interestingly enough, the interface variables in itself are now worthless
// We used them to create the service and that's it, we cannot modify them any more.
CFRelease(topInterface);
CFRelease(bottomInterface);
topInterface = NULL;
bottomInterface = NULL;
topInterface = SCNetworkServiceGetInterface(service);
// Let's apply all configuration to the PPTP interface
// Specifically, the servername, account username and password
if (SCNetworkInterfaceSetConfiguration(topInterface, config.L2TPIPSecConfig)) {
NSLog(#"Successfully configured PPP interface of service %#", config.name);
} else {
NSLog(#"Error: Could not configure PPP interface for service %#", config.name);
return;
}
if (SCNetworkInterfaceSetExtendedConfiguration(topInterface, kSCEntNetIPSec, config.L2TPIPSecConfig)) {
NSLog(#"Successfully configured IPSec on PPP interface for service %#", config.name);
} else {
NSLog(#"Error: Could not configure PPTP on PPP interface for service %#. %s (Code %i)", config.name, SCErrorString(SCError()), SCError());
//return;
}
NSLog(#"Adding default protocols (DNS, etc.) to service %#...", config.name);
if (!SCNetworkServiceEstablishDefaultConfiguration(service)) {
NSLog(#"Error: Could not establish a default service configuration for %#. %s (Code %i)", config.name, SCErrorString(SCError()), SCError());
return;
}
NSLog(#"Fetching set of all available network services...");
SCNetworkSetRef networkSet = SCNetworkSetCopyCurrent(prefs);
if (!networkSet) {
NSLog(#"Error: Could not fetch current network set when creating %#. %s (Code %i)", config.name, SCErrorString(SCError()), SCError());
return;
}
if (!SCNetworkSetAddService (networkSet, service)) {
if (SCError() == 1005) {
NSLog(#"Skipping VPN Service %# because it already exists.", config.humanType);
return;
} else {
NSLog(#"Error: Could not add new VPN service %# to current network set. %s (Code %i)", config.name, SCErrorString(SCError()), SCError());
return;
}
}
NSLog(#"Fetching IPv4 protocol of service %#....", config.name);
SCNetworkProtocolRef protocol = SCNetworkServiceCopyProtocol(service, kSCNetworkProtocolTypeIPv4);
if (!protocol) {
NSLog(#"Error: Could not fetch IPv4 protocol of %#. %s (Code %i)", config.name, SCErrorString(SCError()), SCError());
return;
}
NSLog(#"Configuring IPv4 protocol of service %#...", config.name);
if (!SCNetworkProtocolSetConfiguration(protocol, config.L2TPIPSecConfig)) {
NSLog(#"Error: Could not configure IPv4 protocol of %#. %s (Code %i)", config.name, SCErrorString(SCError()), SCError());
return;
}
NSLog(#"Commiting all changes including service %#...", config.name);
if (!SCPreferencesCommitChanges(prefs)) {
NSLog(#"Error: Could not commit preferences with service %#. %s (Code %i)", config.name, SCErrorString(SCError()), SCError());
return;
}
NSLog(#"Preparing to add Keychain items for service %#...", config.name);
// The password and the shared secret are not stored directly in the System Preferences .plist file
// Instead we put them into the KeyChain. I know we're creating new items each time you run this application
// But this actually is the same behaviour you get using the official System Preferences Network Pane
[VPNKeychain createPasswordKeyChainItem:config.name forService:serviceID withAccount:config.username andPassword:config.password];
[VPNKeychain createSharedSecretKeyChainItem:config.name forService:serviceID withPassword:config.sharedSecret];
if (!SCPreferencesApplyChanges(prefs)) {
NSLog(#"Error: Could not apply changes with service %#. %s (Code %i)", config.name, SCErrorString(SCError()), SCError());
return;
}
NSLog(#"Successfully created %# VPN %# with ID %#", config.humanType, config.name, serviceID);
return;
}
I tried this :
how to set up a vpn connection programmatically in MAC?
Update :
Code I used to auto connect to vpn server
SCNetworkConnectionRef connetionRef = SCNetworkConnectionCreateWithServiceID(kCFAllocatorDefault, (__bridge CFStringRef)(newServiceId), myCallBack, &callBackContext);
SCNetworkConnectionStart(connetionRef, config.L2TPPPPConfig, FALSE);

Related

Need create VPN connect L2TP on osx

Need create VPN connection L2TP on osx without Shared Secret
NSString *server = #"serverIP";
NSString *username = #"user";
NSString *password = #"pass";
const void* passwordData = [[password dataUsingEncoding:NSUTF8StringEncoding] bytes];
[vpnManager loadFromPreferencesWithCompletionHandler:^(NSError *error) {
if (error) {
NSLog(#"Load config failed [%#]", error.localizedDescription);
return;
}
NEVPNProtocol *p = (NEVPNProtocol *)vpnManager.protocolConfiguration;
if (!p) {
p = [[NEVPNProtocol alloc] init];
}
p.username = username;
p.serverAddress = server;
p.passwordReference = (__bridge NSData * _Nullable)(passwordData);
p.disconnectOnSleep = NO;
vpnManager.protocolConfiguration = p;
vpnManager.localizedDescription = #"L2TPOverIPSec";
vpnManager.enabled = YES;
[vpnManager saveToPreferencesWithCompletionHandler:^(NSError *error) {
if (error) {
NSLog(#"Save config failed [%#]", error.localizedDescription);
}
}];
}];
NEVPNConnection *connect = [vpnManager connection];
NSError *error1;
if ([connect startVPNTunnelAndReturnError: &error1]) {
NSLog(#"connect");
} else {
NSLog(#"not connect");
}
after building i get this error Missing protocol or protocol has invalid type, and other 2 protocols use IKE tunnel, what can u advice to me? other option is run terminal from code and add this string networksetup -connectpppoeservice VPNConnect but i dont know if it possible
I know this is the old question, but I'm here to note, that Network Extension framework (where NEVPNProtocol is from) can't go on with OSI level 2 protocols, which is L2TP. (https://forums.developer.apple.com/thread/29909)
It seems now (starting from iOS 8 and os x 10.10) the recommended way on Apple devices is to use built-in protocols, or implement your own but on L3 / L4: https://developer.apple.com/documentation/networkextension
(And so there is no public API for using L2TP)
No, you need use SCNetwork and Helper Tool (to get root access) With this link you will be able to create L2TP Protocol and connect with it.
This works even on the latest version MacOS 11 "Big Sure"

Copy client identity to private keychain on Mac OS

Background
I'm working on big mutiplatform library where client identity is used. Requirement is that application maintains client identity privately and it is possible to import client identity from system store (in case of Mac OS default keychain).
So basically API has to cover following
set client identity from default keychain
set client identity form file (system keychian should not see it)
delete client identity (this should not break other applications)
if identity will be deleted from default keychain (using Keychain Access for example) my application should not be impacted
Basic problem
Initially I planed store identity in some private encrypted file, but it turn out that Apple API doesn't allow to import client identity without presence of a keychain.
So I've decided that my library will maintain private keychain. In case of importing identity form file it is quite simple and it does work:
NSDictionary *options =
#{
(id)kSecImportExportPassphrase:password.stringValue,
(id)kSecImportExportKeychain:(__bridge id)keychain,
};
CFArrayRef arrayResult = NULL;
OSStatus status = SecPKCS12Import((CFDataRef)pkcs12,
(CFDictionaryRef)options,
&arrayResult);
if (status != errSecSuccess)
Now problem is how to copy client identity form default keychian to private keychain?
Here is a code I used to create a keychain:
- (void)setupOpenMyKeychain
{
NSString *keychainPath = [self keychainPath];
NSString *pass = #"dasndiaisdfs"; // used only for testing
OSStatus status = SecKeychainCreate(keychainPath.UTF8String,
(UInt32)strlen(pass.UTF8String),
pass.UTF8String,
NO,
NULL,
&keychain);
if (status == errSecDuplicateKeychain)
{
status = SecKeychainOpen(keychainPath.UTF8String, &keychain);
if (status == errSecSuccess)
{
status = SecKeychainUnlock(keychain,
(UInt32)strlen(pass.UTF8String),
pass.UTF8String,
TRUE);
if (status != errSecSuccess)
{
[self showOSStatusFailureAlert: status forAction: #"Unlock failure"];
}
}
}
if (status != errSecSuccess)
{
[self showOSStatusFailureAlert: status forAction: #"Open/Create failure"];
}
}
Now I have SecIdentityRef and SecCertificate (in CFArrayRef) in a default keychain and I want to copy it to my custom keychian:
- (NSData *)persistantRefFor: (id)item
{
CFTypeRef result = NULL;
NSDictionary *dic =
#{
(id)kSecValueRef:(id)item,
(id)kSecReturnPersistentRef:(id)kCFBooleanTrue,
//(id)kSecUseKeychain:(__bridge id)keychain,
};
OSStatus status = SecItemCopyMatching((CFDictionaryRef)dic,
&result);
if (status != errSecSuccess)
{
[self showOSStatusFailureAlert: status
forAction: #"Failed to find reference"];
return nil;
}
return (__bridge_transfer NSData *)result;
}
-(OSStatus)copyItemWithPersistantRef:(NSData *)persistantReference
{
SecKeychainItemRef item = NULL;
OSStatus result = SecKeychainItemCopyFromPersistentReference((__bridge CFDataRef)persistantReference, &item);
if (result != errSecSuccess)
{
[self showOSStatusFailureAlert: result forAction: #"Get item from reference"];
return result;
}
CFAutorelease(item);
SecKeychainRef soruceKeychain = NULL;
result = SecKeychainItemCopyKeychain(item, &soruceKeychain);
if (result == errSecSuccess)
{
if (soruceKeychain == keychain)
{
CFRelease(soruceKeychain);
// item is already in desired keychain
return result;
}
} else {
[self showOSStatusFailureAlert: result forAction: #"Fetching source keychain"];
return result;
}
SecAccessRef access = NULL;
result = SecKeychainCopyAccess(keychain, &access);
if (result != errSecSuccess)
{
[self showOSStatusFailureAlert: result forAction: #"Get Access object"];
// return result;
} else {
CFAutorelease(access);
}
SecKeychainItemRef itemCopy = NULL;
result = SecKeychainItemCreateCopy(item, keychain, access, &itemCopy);
if (result != errSecSuccess)
{
[self showOSStatusFailureAlert: result forAction: #"Create copy in private keychain"];
return result;
}
CFAutorelease(itemCopy);
NSLog(#"Copied item %#", itemCopy);
NSData *copyPersistantRef = [self persistantRefFor: (__bridge id)itemCopy];
NSLog(#"Old persisntatn reference %#\n"
"New persisntatn reference %#\n"
"%#", persistantReference, copyPersistantRef,
[persistantReference isEqualToData: copyPersistantRef]?#"SAME":#"DIFFRENT");
return result;
}
- (IBAction)copyIdentityToPrivateKeychain:(id)sender
{
if (!identity)
{
return;
}
NSData *perRef = [self persistantRefFor: (__bridge id)identity];
OSStatus status = [self copyItemWithPersistantRef: perRef];
if (status != errSecSuccess)
{
[self showOSStatusFailureAlert: status forAction: #"Copy Identity has failed"];
}
}
When trying to copy SecCertificateRef to my private keychain. Following things happen:
SecKeychainCopyAccess fails with "Error: 0xFFFFFFFC -4 Function or operation not implemented." - code continues execution since apparently NULL value is fine when reaching SecKeychainItemCreateCopy (documentation sugest that is should work).
SecKeychainItemCreateCopy passes Ok
NSData *copyPersistantRef = [self persistantRefFor: (__bridge id)itemCopy]; fails. I'm unable to get persistent reference to copied item. Without this reference I'm unable to load proper certificate when application starts
It is worse when trying to copy SecIdentityRef,
code fails on SecKeychainItemCopyKeychain with Error: 0xFFFF9D28 -25304 The specified item is no longer valid. It may have been deleted from the keychain.
when I forced to skip this error to reach SecKeychainItemCreateCopy it is failing with same error: Error: 0xFFFF9D28 -25304 The specified item is no longer valid. It may have been deleted from the keychain.
So bottom line according to documentation it suppose to work, but it doesn't work. Probably I'm doing something incorrectly, but I'm unable to find what is the problem.
Any feedback will be appreciated.
Same question I've posted on Apple forum.
Need to support OS X 10.10.
Here is a small test application I'm using for quick testing.
Instead of SecPKCS12Import, use SecItemImport. Pass kSecFormatPKCS12 as inputFormat, kSecItemTypeAggregate as itemType (to require identities or to kSecItemTypeUnknown and it will try to detect the type for you) and set importKeychain to NULL, which prevents import to keychain.

associate arbitrary information with the socket using GCDAyncSocket

I have created a simple client server application with GCDAsyncSocket.
host broadcast its service via bonjour, clients get connected to it, then each client send specific information to host, after completing requests by host, it should now be able to send each request to its corresponding client.
Here when client disconnected (maybe rebooting WiFi) before It gets its completed request and connected again its connected host and port can change.
so for I denitrifying clients I want to store list of connected clients in a dictionary
key : vendorID (specific to each client and can't change)
value : connected host and port (gcdAsyncSocket instance)
how can I send arbitrary information (vendorID) with socket?
it seems GCDAsyncSocket has a property named UserData, I set it with vendorID in the client
but in the host it's NULL.
client:
- (BOOL)connectWithService:(NSNetService *)service {
BOOL _isConnected = NO;
// Copy Service Addresses
NSArray *addresses = [[service addresses] mutableCopy];
if (!self.socket || ![self.socket isConnected]) {
// Initialize Socket
self.socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
//adding deviceID to sockect to identify connected clients in the host
self.socket.userData = #"vendorID2308746238764021";
// Connect
while (!_isConnected && [addresses count]) {
NSData *address = [addresses objectAtIndex:0];
NSError *error = nil;
if ([self.socket connectToAddress:address error:&error]) {
_isConnected = true;
}
else if (error) {
NSLog(#"Unable to connect to address. Error %# with user info %#.", error, [error userInfo]);
}
}
} else {
_isConnected = [self.socket isConnected];
}
return _isConnected;
}
Host:
- (void)socket:(GCDAsyncSocket *)socket didAcceptNewSocket:(GCDAsyncSocket *)newSocket {
NSLog(#"Accepted New Socket from %#:%hu", [newSocket connectedHost], [newSocket connectedPort]);
//both userData is null!
NSLog(#"%#",newSocket.userData);
NSLog(#"%#",socket.userData);
//adding new socket to the list of connected clients
//using ip and port of the scoket as key
[self.clients setObject:newSocket forKey:[NSString stringWithFormat:#"%#-%d", [newSocket connectedHost], [newSocket connectedPort]]];
// Notify Delegate
[self.delegate tcpHost:self didHostClientOnSocket:newSocket];
}
does anyone has any idea how should I solve this kind of client-host information exchange?
ClientSide: In my TCPClient class when client connected to the server, the first packet I send is Identity packet:
//server can identify device
- (void)sendIdentity
{
NSData *identifcationData = [[UIDevice deviceID] dataUsingEncoding:NSUTF8StringEncoding];
HAPacket *packet = [[HAPacket alloc] initWithData:identifcationData type:HAPacketTypeIdentity action:HAPacketActionUnknown];
[self sendPacket:packet];
}
Server: gets this packet and identify the Client
In TCPHost: after client connected I store it in a temporary array.
in socket:didReadData I check:
if (tag == TAG_BODY) {
//first packet is always for identifaction. if client has not identified, its id is empty
Client *unknownClient = [[self.temp linq_where:^BOOL(Client *client)
{
return client.ID == nil || [client.ID isEqualToString: #""];
}] linq_firstOrNil];
if (unknownClient && (unknownClient.ID == nil || [unknownClient.ID isEqualToString: #""])) {
NSLog(#"identifying client...");
[self identifyClient:unknownClient withData:data];
}
else
{
// parse the packet as usual
}

Threading and Sockets in Objective-C

NOTE: I've edited my question. I've got it to connect and perform the first callback, but subsequent callbacks don't go through at all.
This is my first time writing Objective-C (with GNUstep; it's for a homework assignment). I've got the solution working, but I am trying to add something more to it. The app is a GUI client that connects to a server and gets data from it. Multiple clients can connect to the same server. If any one of the clients changes data that is residing on the server, the server sends a callback to all registered clients. This solution was originally implemented in Java (both client and server) and for the latest assignment, the professor wanted us to write an Objective-C client for it. He said that we don't need to handle callbacks, but I wanted to try anyway.
I am using NSThread and I wrote something that looks like this:
CallbackInterceptorThread.h
#import <Foundation/Foundation.h>
#import "AppDelegate.h"
#interface CallbackInterceptorThread : NSThread {
#private
NSString* clientPort;
AppDelegate* appDelegate;
}
- (id) initWithClientPort: (NSString*) aClientPort
appDelegate: (AppDelegate*) anAppDelegate;
- (void) main;
#end
CallbackInterceptorThread.m
#import <Foundation/Foundation.h>
#import "CallbackInterceptorThread.h"
#define MAXDATASIZE 4096
#implementation CallbackInterceptorThread
- (id) initWithClientPort: (NSString*) aClientPort
appDelegate: (AppDelegate*) anAppDelegate {
if((self = [super init])) {
[clientPort autorelease];
clientPort = [aClientPort retain];
[appDelegate autorelease];
appDelegate = [anAppDelegate retain];
}
return self;
}
- (void) main {
GSRegisterCurrentThread();
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
char* buffer = malloc(MAXDATASIZE);
Cst420ServerSocket* socket = [[Cst420ServerSocket alloc] initWithPort: clientPort];
[socket retain];
NSString* returnString;
while(YES) {
printf("Client waiting for callbacks on port %s\n", [clientPort cString]);
if([socket accept]) {
printf("Connection accepted!\n");
while(YES) {
printf("Inner loop\n");
sleep(1);
returnString = [socket receiveBytes: buffer maxBytes: MAXDATASIZE beginAt: 0];
printf("Received from Server |%s|\n", [returnString cString]);
if([returnString length] > 0) {
printf("Got a callback from server\n");
[appDelegate populateGui];
}
printf("Going to sleep now\n");
sleep(1);
}
[socket close];
}
}
}
#end
Cst420ServerSocket has been provided to us by the instructor. It looks like this:
#import "Cst420Socket.h"
#define PORT "4444"
/**
* Cst420Socket.m - objective-c class for manipulating stream sockets.
* Purpose: demonstrate stream sockets in Objective-C.
* These examples are buildable on MacOSX and GNUstep on top of Windows7
*/
// get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa){
if (sa->sa_family == AF_INET) {
return &(((struct sockaddr_in*)sa)->sin_addr);
}
return &(((struct sockaddr_in6*)sa)->sin6_addr);
}
#implementation Cst420ServerSocket
- (id) initWithPort: (NSString*) port{
self = [super init];
int ret = 0;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE; // use my IP
const char* portStr = [port UTF8String];
if ((rv = getaddrinfo(NULL, portStr, &hints, &servinfo)) != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
ret = 1;
}else{
for(p = servinfo; p != NULL; p = p->ai_next) {
if ((sockfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP))==-1){
perror("server: socket create error");
continue;
}
if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
#if defined(WINGS)
closesocket(sockfd);
#else
close(sockfd);
#endif
perror("server: bind error");
continue;
}
break;
}
if (p == NULL) {
fprintf(stderr, "server: failed to bind\n");
ret = 2;
}else{
freeaddrinfo(servinfo); // all done with this structure
if (listen(sockfd, BACKLOG) == -1) {
perror("server: listen error");
ret = 3;
}
}
if (ret == 0){
return self;
} else {
return nil;
}
}
}
- (BOOL) accept {
BOOL ret = YES;
#if defined(WINGS)
new_fd = accept(sockfd, NULL, NULL);
#else
new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size);
#endif
if (new_fd == -1) {
perror("server: accept error");
ret = NO;
}
connected = ret;
return ret;
}
- (int) sendBytes: (char*) byteMsg OfLength: (int) msgLength Index: (int) at{
int ret = send(new_fd, byteMsg, msgLength, 0);
if(ret == -1){
NSLog(#"error sending bytes");
}
return ret;
}
- (NSString* ) receiveBytes: (char*) byteMsg
maxBytes: (int) max
beginAt: (int) at {
int ret = recv(new_fd, byteMsg, max-1, at);
if(ret == -1){
NSLog(#"server error receiving bytes");
}
byteMsg[ret+at] = '\0';
NSString * retStr = [NSString stringWithUTF8String: byteMsg];
return retStr;
}
- (BOOL) close{
#if defined(WINGS)
closesocket(new_fd);
#else
close(new_fd);
#endif
connected = NO;
return YES;
}
- (void) dealloc {
#if defined(WINGS)
closesocket(sockfd);
#else
close(sockfd);
#endif
[super dealloc];
}
#end
Our professor also provided us an example of a simple echo server and client (the server just spits back whatever the client sent it) and I've used the same pattern in the thread.
My initial problem was that my callback interceptor thread didn't accept any (callback) connections from the server. The server said that it could not connect back to the client (ConnectException from Java; it said "Connection refused"). I was able to fix this by changing my instructor's code. In the connect function (not shown), he had set the hints to use AF_UNSPEC instead of AF_INET. So Java was seeing my localhost IP come through as 0:0:0:0:0:0:0:1 (in IPv6 format). When Java tried to connect back to send a callback, it received an exception (not sure why it cannot connect to an IPv6 address).
After fixing this problem, I tried out my app again and this time the callback from the server was received by my client. However, subsequent callbacks fail to work. After receiving the first callback, the busy-loop keeps running (as it should). But when the server sends a second callback, it looks like the client cannot read it in. On the server side I can see that it sent the callback to the client successfully. It's just that the client is having trouble reading in the data. I added some print statements (see above) for debugging and this is what I get:
Client waiting for callbacks on port 2020
Connection accepted!
Inner loop
Received from Server |A callback from server to 127.0.0.1:2020|
Got a callback from server
Going to sleep now
Inner loop
Received from Server ||
Going to sleep now
Inner loop
Received from Server ||
Going to sleep now
Inner loop
... (and it keeps going regardless of the second callback being sent)
Here is how I am starting the thread (from the GUI):
CallbackInterceptorThread* callbackInterceptorThread = [[CallbackInterceptorThread alloc] initWithClientPort: clientPort appDelegate: self];
[callbackInterceptorThread start];
I think I've got it working. So from the Java side (the server), this was what I was doing:
Socket socket = new Socket(clientAddress, clientPort);
BufferedOutputStream out = new BufferedOutputStream(socket.getOutputStream());
out.write(("A callback from server to " + clientAddress + ":" + clientPort).getBytes());
out.flush();
out.close();
I put some debugging print-statements in my professor's code and noticed that in receiveBytes, recv was returning 0. The return value of recv is the length of the message that it received. So it received a zero-length string. But a return value of 0 also means that the peer closed the connection properly (which is exactly what I had done from the Java side with out.close()). So I figured that if I needed to respond to the second callback, I would need to accept the connection again. So I changed my busy loop to this:
printf("Client waiting for callbacks on port %s\n", [clientPort cString]);
while([socket accept]) {
printf("Connection accepted!\n");
returnString = [socket receiveBytes: buffer maxBytes: MAXDATASIZE beginAt: 0];
printf("Received from Server |%s|\n", [returnString cString]);
if([returnString length] > 0) {
printf("Got a callback from server\n");
[appDelegate populateGui];
}
}
[socket close];
and that seemed to do the trick. I am not sure if this is the right way to do it, so I am open to suggestions for improvement!

Help with Sending/ Receiving UDP packets - C Sockets

Ok, if you look at some of my previous questions, I've been working on getting a simple connection up and running with C sockets (I'm still fairly new to the whole networking aspect of an program, but everyone has to start somewhere, right?). I've included the code below that I have so far and when I execute it, I get no errors, but at the same time, I don't get the packet on the other end. By the way, I'm programming multicast sockets in objective-C and "msgStatus" is just a label in my GUI (it's hooked up correctly, so there's no problem there). I'm just not seeing where I'm going wrong. Could someone possibly help me or point me in the right direction? Thanks!
#define MAX_LEN 1024 /* maximum string size to send */
#define MIN_PORT 1024 /* minimum port allowed */
#define MAX_PORT 65535 /* maximum port allowed */
#define MYPORT 5673 /* port we will be using for our multicast socket */
-(void)broadcastMessage {//(NSString*)msg {
NSLog(#"broadcastMessage - Stage 1");
NSString *msg = #"From Master";
mc_ttl = 3; // number of node hops the message is allowed to travel across the network
// define the port we will be using
mc_port = MYPORT;
/* create a socket for sending to the multicast address */
if ((sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
NSLog(#"ERROR: broadcastMessage - socket() failed");
return;
}
mc_addr.sin_family = AF_INET;
mc_addr.sin_addr.s_addr = inet_addr("225.0.0.37");
mc_addr.sin_port = htons(mc_port);
if (bind(sock, (struct sockaddr *) &mc_addr, sizeof(struct sockaddr_in)) < 0) {
NSLog(#"ERROR: bind not successful");
return;
}
NSLog(#"broadcastMessage - Stage 2");
/* set the TTL (time to live/hop count) for the send */
if ((setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, (void*) &mc_ttl, sizeof(mc_ttl))) < 0) {
NSLog(#"ERROR: broadcastMessage - setsockopt() failed");
return;
}
NSLog(#"broadcastMessage - Stage 3");
/* construct a multicast address structure - erase everything in the structure first*/
memset(&mc_addr, 0, sizeof(mc_addr));
// prepare the message to be sent
char send_str[MAX_LEN];
/* clear send buffer */
memset(send_str, 0, sizeof(send_str));
// convert the message to a C string to send
[msg getCString:send_str maxLength:MAX_LEN encoding:NSASCIIStringEncoding];
//while (fgets(send_str, MAX_LEN, stdin)) {
NSLog(#"broadcastMessage - Stage 4");
// send_len = strlen(send_str);
/* send string to multicast address */
if ((sendto(sock, send_str, sizeof(send_str), 0, (struct sockaddr *) &mc_addr, sizeof(mc_addr))) != sizeof(send_str)) {
NSLog(#"ERROR: broadcastMessage - sendto() sent incorrect number of bytes");
return;
}
NSLog(#"broadcastMessage - Stage 5");
/* clear send buffer */
memset(send_str, 0, sizeof(send_str));
NSLog(#"broadcastMessage - Stage 6");
close(sock);
}
-(void)listenForPackets {
listeningFlag_on = 1;
NSLog(#"listenForPackets - Stage 1");
if ((listeningSock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
NSLog(#"ERROR: listenForPackets - socket() failed");
return; // make the method return an int instead of void and use this statement to check for errors
}
// set reuse port to on to allow multiple binds per host
if ((setsockopt(listeningSock, SOL_SOCKET, SO_REUSEADDR, &listeningFlag_on, sizeof(listeningFlag_on))) < 0) {
NSLog(#"ERROR: listenForPackets - setsockopt() failed");
return; // make the method return an int instead of void and use this statement to check for errors
}
NSLog(#"listenForPackets - Stage 2");
// construct a multicast address structure after erasing anything in the listeningmc_addr structure
memset(&listeningmc_addr, 0, sizeof(listeningmc_addr));
listeningmc_addr.sin_family = AF_INET;
listeningmc_addr.sin_addr.s_addr = htonl(INADDR_ANY);
listeningmc_addr.sin_port = htons(mc_port);
// bind multicast address to socket
if ((bind(listeningSock, (struct sockaddr *) &listeningmc_addr, sizeof(listeningmc_addr))) < 0) {
NSLog(#"ERROR: listenForPackets - bind() failed");
return; // make the method return an int instead of void and use this statement to check for errors
}
//******************************************************************************************************************************
//******************************************************************************************************************************
NSString *ipAddress = [[NSString alloc] initWithString:self.getIPAddress];
const char *tmp = [ipAddress UTF8String];
listeningMc_addr_str = tmp;
printf("%s\n", listeningMc_addr_str);
listeningMc_req.imr_multiaddr.s_addr = inet_addr("225.0.0.37");
listeningMc_req.imr_interface.s_addr = htonl(INADDR_ANY);
// send an ADD MEMBERSHIP message via setsockopt
if ((setsockopt(listeningSock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void*) &listeningMc_req, sizeof(listeningMc_req))) < 0) {
NSLog(#"ERROR: listenForPackets - setsockopt() failed");
int err = errno;
NSLog(#"errno - %i", err);
NSLog(#"Error = %s", strerror(err));
perror("ERROR");
return; // make the method return an int instead of void and use this statement to check for errors
}
NSLog(#"listenForPackets - Stage 3");
for (;;) { // loop forever
// clear the receive buffers & structs
memset(listeningRecv_str, 0, sizeof(listeningRecv_str));
listeningFrom_len = sizeof(listeningFrom_addr);
memset(&listeningFrom_addr, 0, listeningFrom_len);
// block waiting to receive a packet
if ((listeningRecv_len = recvfrom(listeningSock, listeningRecv_str, MAX_LEN, 0, (struct sockaddr*)&listeningFrom_addr, &listeningFrom_len)) < 0) {
NSLog(#"ERROR: listenForPackets - recvfrom() failed");
return; // make the method return an int instead of void and use this statement to check for errors
}
NSLog(#"listenForPackets - Stage 4");
NSString *tmpy = [[NSString alloc] initWithCString:listeningRecv_str encoding:NSASCIIStringEncoding];
msgStatus.text = tmpy;
NSLog(#"ERROR");
}
// received string
printf("Received %d bytes from %s: ", listeningRecv_len, inet_ntoa(listeningFrom_addr.sin_addr));
printf("%s", listeningRecv_str);
}
// send a DROP MEMBERSHIP message via setsockopt
if ((setsockopt(listeningSock, IPPROTO_IP, IP_DROP_MEMBERSHIP, (void*) &listeningMc_req, sizeof(listeningMc_req))) < 0) {
NSLog(#"ERROR: listenForPackets - setsockopt() failed");
//return 1; // make the method return an int instead of void and use this statement to check for errors
}
close(listeningSock);
NSLog(#"listenForPackets - Stage 5 - Complete");
}
Here is the code I'm using to extract my IP Address.
-(NSString *)getIPAddress {
NSString *address = #"error";
struct ifaddrs *interfaces; // = NULL;
struct ifaddrs *temp_addr; // = NULL;
int success = 0;
// retrieve the current interfaces - returns 0 on success
success = getifaddrs(&interfaces);
if (success == 0)
{
// Loop through linked list of interfaces
temp_addr = interfaces;
while(temp_addr != NULL)
{
if(temp_addr->ifa_addr->sa_family == AF_INET)
{
// Check if interface is en0 which is the wifi connection on the iPhone
if([[NSString stringWithUTF8String:temp_addr->ifa_name] isEqualToString:#"en0"])
{
// Get NSString from C String
address = [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)temp_addr->ifa_addr)->sin_addr)];
}
}
temp_addr = temp_addr->ifa_next;
}
}
// Free memory
freeifaddrs(interfaces);
return address;
}
In the listener, I think you need to set
listeningMc_req.imr_interface.s_addr = htonl(INADDR_ANY);
... since that is also the interface on which you bind the socket. Depending on whether you run everything on a single host, you might need to consider the loopback interface and binding to INADDR_ANY will do that.
Is there a router between you and your destination? If so, there's some work that needs to be done to tell the router that you want to subscribe to the feed as well as tell the router you will be sending the feed.
I would start by tcpdump-ing the connection to make sure the packet is leaving your machine first.